[내일배움캠프/C++] 포인터와 레퍼런스

김세희·2025년 5월 28일
post-thumbnail

✍️Today I Learned

  1. STL 자료구조
  2. 범위기반 for 문
  3. C++ 문법: 포인터와 레퍼런스

STL 자료구조

문제 해결을 위해 로직을 구상할 때 큰 틀을 먼저 구상하고 이것에 맞는 자료구조가 뭔지 알아야 한다. 이번에는 아래의 STL 선택 가이드를 참고하여 문제를 풀었다.

연습문제
주어진 단어의 각 문자를 하나씩 뒤로 이동하여 만들어진 모든 회전된 단어를 출력

순서 중요-> 정렬 상태 유지->중간에 지우거나 삽입하지 않음->Persistent Position라고 생각해 List를 반환값으로 골랐다.

list<string> charRotation(string str) {
	list<string> charRotation;
	for (const auto c : str) {
		char c = str[0];
		charRotation.push_back(str);
		str.push_back(c);
		str.erase(0, 1);
	}
	return charRotation;
}

범위기반 for문

// 
for ( element-declaration : expression )
  statement
  • 배열을 자동으로 인식한다.
  • .begin() 및 .end()가 포함된 컨테이너를 인식합니다.
  • 기타 항목에 대해 begin() 및 end() 인수 종속성 조회를 사용합니다.
  • ❗범위기반 for문 사용시 주의할 점

    // 범위 기반 for문
    for (int n : nums) {
        n *= 2;  // 원본 수정 불가능
    }
    // 범위 기반 for문
    for (int& n : nums) {
        n *= 2;  // 원본 수정 가능
    }
    // 이렇게 변환됨
    for (auto it = std::begin(nums); it != std::end(nums); ++it) {
        int n = *it;  // 복사본 생성
    }

    범위 기반 for문은 마지막 for문의 원리로 동작하므로 n은 nums요소의 복사본이다. for문에서 해당 배열의 원본을 수정하려면 element를 레퍼런스로 선언해야한다.


    포인터와 레퍼런스

    🎯포인터

    int* p : p는 int 변수의 메모리 주소를 저장하는 포인터

    1. 변수의 주소값을 담을 수 있다. A = &B; //B의 주소값, A가 포인터일때만 가능
    2. 담고 있는 주소값에 해당되는 메모리에 있는 값을 읽거나 수정할 수 있다.

    포인터 변수의 구성요소

    1. 변수의 시작 주소
    2. 변수의 타입

    👀👉포인터에 변수 타입이 필요한 이유
    주소값을 따라가서 값을 읽으려면 주소를 기준으로 몇 바이트를 읽을 지가 필요해서 어떤 타입 변수의 주소인지가 필요함 ex) int* -> 주소로 가서 4byte만큼 읽음

    문자형 포인터 char 은 출력할 때 주의: (void*)ptr 로 출력해야 안깨짐

    복사비용을 생각하면 다른 변수에 바로 대입하는거보다 주소값만 들고오는게 나음

    포인터 역참조(*)

    역참조연산자: *

    *ptr = 10; // ptr이 가리키는 변수의 값을 변경할 수 있다.

    배열의 포인터

    배열은 주소값을 가지고 있는 변수. 여러 변수를 묶어서 관리한다.
    배열의 이름은 시작 주소를 담고 있어 변경할 수 없다(상수 포인터).
    arr:시작 주소값 arr[0]: 시작 주소에 들어있는 변수의 값
    arr[i] = *(arr+i)

    int arr[3] = {10, 20, 30};
    int* p = arr; // 배열의 시작 주소를 포인터에 저장, 
      			//배열의 이름은 배열의 시작 주소를 갖는다. 따라서 주소연산자 필요 없음

    *p : p가 가리키는 값
    *(p+1) : p+1이 가리키는 값. p는 int* 이기 때문에 +1 이면 4byte씩 증가해서 arr[1]의 주소

    arr[1] = *(p + 1) = p[1]: 배열 첨자 연산자는 포인터에서도 쓸 수 있음

    포인터 배열과 배열 포인터

    배열 포인터: 배열을 가리키는 포인터 int arr[4]; int* ptr = arr;는 크기가 4인 배열 arr를 가리키는 포인터.

    2차원 배열쓸 때 사용함

    #include <iostream>
        using namespace std;
        
        // 배열 포인터: 배열 전체를 가리키는 포인터
        int main() {
            int arr[3] = { 100, 200, 300 };
            int (*ptr)[3] = &arr; // 배열 포인터 선언
        
            // 배열 포인터를 이용하여 배열 요소 접근
            cout << "(*ptr)[0]: " << (*ptr)[0] << endl; // 100
            cout << "(*ptr)[1]: " << (*ptr)[1] << endl; // 200
            cout << "(*ptr)[2]: " << (*ptr)[2] << endl; // 300
        
            return 0;
        }
        
        /*
        출력 결과:
        (*ptr)[0]: 100
        (*ptr)[1]: 200
        (*ptr)[2]: 300
        */

    포인터 배열: 배열의 각 원소가 포인터 int* ptrArr[4];는 크기가 4이고 각 원소가 int*인 배열

    #include <iostream>
        using namespace std;
        
        // 포인터 배열: 포인터를 원소로 갖는 배열
        int main() {
            int a = 10, b = 20, c = 30;
            int* ptrArr[3] = { &a, &b, &c }; // 포인터 배열 선언 및 초기화
        
            // 포인터 배열을 이용하여 값 출력
            cout << "*ptrArr[0]: " << *ptrArr[0] << endl; // 10
            cout << "*ptrArr[1]: " << *ptrArr[1] << endl; // 20
            cout << "*ptrArr[2]: " << *ptrArr[2] << endl; // 30
        
            return 0;
        }
        
        /*
        출력 결과:
        *ptrArr[0]: 10
        *ptrArr[1]: 20
        *ptrArr[2]: 30
        */

    ❗포인터 사용시 주의사항
    포인터를 초기화하지 않고 불러오거나 이상한 주소로 역참조를 하는 경우 예기치 못한 에러로 종료된다. 일반 변수 사용할 때보다 리스크가 크므로 조심해서 사용해야 한다.

    🎯레퍼런스

    포인터는 주소값을 직접 다루어야 하므로 복잡해질 수 있는 문제를 해결하기 위해 레퍼런스 문법을 도입했다.
    레퍼런스는 특정 변수의 별명이다.
    일반 변수와 거의 동일하게 사용할 수 있다. 내부적으로 해당 변수를 직접 가리키는 역할을 한다.
    역참조* 없이도 변수의 값을 변경할 수 있는 문법
    📢값을 복사하지 않고 대상에 직접 접근하는 안전하고 간결한 방법

    int x=3;
    int y=x;
    y=4; // x=3;	x와 y는 메모리가 따로 잡힘
    int& z=x;
    z=4; // x=4;	x와 z는 같은 메모리를 공유	

    ❗레퍼런스 사용시 주의사항

    선언과 동시에 초기화해야 한다.
    초기화 이후 다른 변수에 연결할 수 없다.

    ✨레퍼런스 쓰는 이유

    1. 함수 인자 전달 시 복사 비용 줄이기 + 수정 가능
      함수에 인자로 일반 변수를 입력할 때 새로운 메모리를 할당받아 값을 복사하여 전달하므로 메모리도 잡아먹고 함수 안에서 값을 변경해도 main()에서는 바뀌지 않음

      void change(int& x) {
      	x = 100;
      }
      
      int main() {
      	int a = 10;
      	change(a);  // a는 100으로 바뀜!
      }
      
    2. 함수 리턴값으로 참조 반환
      함수 리턴을 참조로 하면 l value로 쓸 수 있음

      int& getElement(int arr[], int index) {
      	return arr[index];  // 참조 반환
      }
      
      getElement(arr, 1) = 42;  // arr[1] = 42;
      

    🎯포인터와 레퍼런스 차이점

    1. 선언과 초기화 시점
    2. 레퍼런스는 항상 다른 변수와 연결되어 있어 NULL일 수 없음
      포인터는 유효한 대상이 없으면 NULL이나 nullptr 을 가짐
      NULL => (void*)0 0인지 0번째 주소값인지 애매한 상황 발생
      nullptr => NULL의 애매함을 해결했기 때문에 이거 사용!
    3. 간접참조 문법의 유무
      레퍼런스는 포인터와 달리 일반 변수와 연산하는 방법이 동일하다.

    0개의 댓글