C++ 포인터/참조 연산자는 쪼개서 이해해야 쉽다.
처음 학교에서 C++를 배울 때는 선언(declare)에서의 *와 &, 표현(expression)에서의 *와 &를 구분해서 이해하지 못했다.
C++ 참조와 포인터가 어려운 이유 중 하나는 같은 기호가 상황에 따라 다르게 동작하기 때문이다.
하나의 개념으로 묶기보다 각 기호의 역할을 나누어 이해하면 쉽다.
(1) 참조 &
이퀄 연산자 왼쪽에 있는 &, 즉 변수 타입을 선언하는 데 사용된 &는, 왼쪽의 변수가 오른쪽의 값을 부르는 또 다른 이름이라는 뜻이 된다.
즉, "같은 사람을 부르는 여러 별명 중 하나"로 동작한다.
int a = 0;
int& b = a; // b는 a를 부르는 또 다른 이름. b를 수정하면 a가 수정됨.
b = 5; // a도 5가 된다. 실체는 하나만 존재하고 a와 b는 같은 실체를 부르는 서로 다른 별명.
(2) 주소 &
이퀄 연산자 오른쪽에 있는 &, 또는 이미 선언된 변수나 값에 대한 연산자로 쓰는 &는, 값이 위치한 주소를 얻는다.
int a = 10;
print(&a); // a의 메모리 주소값이 나옴.
int* adressOfa = &a; // 주소를 까서, 주소 저장용 타입에 주소를 저장
(3) 함수 입력에 선언된 참조 &
함수 입력에 있는 &는 입력값의 복제본이 아니라 원본을 받겠다는 뜻이다. C#의 ref나 out 키워드에 대응된다. 즉, 함수 내부에서 입력된 값에 적용한 변경사항이 함수 외부에서도 그대로 적용된다.
처음부터 타입으로서의 &만 떠올리면 이해하기 어렵다. 물론, 사실 이 동작은 (1)의 연장선이다. 함수에 입력된 값의 복제본이 아닌, 입력한 값을 가리키는 또 다른 별명을 만들어 함수 내부에서 쓰겠다는 이야기다.
Change(int& a) {
a = 10; // 함수를 호출한 측에서도 입력값이 10으로 변경됨
}
a = 5;
Change(a);
print(a); // a가 10이 되어있음.
(4) 포인터 *
이퀄 연산자 왼쪽에 있는 *는, 즉 변수 타입을 선언하는 데 사용된 *는 주소를 저장하는 타입을 나타낸다.
포인터는 친구의 집 주소다. 참조는 친구를 부르는 별명 중 하나다.
참조의 값을 수정하면, 실체가 수정된다. 포인터 타입의 변수는, 그 값을 수정한다고 실체가 수정되지 않는다. 포인터 타입의 변수가 가리키는 곳에 도착해서 값을 수정해야 실체가 수정된다.
주소를 메모한 종이에 뭘 한다고, 실제로 무슨 일이 일어나는 게 아니다. 주소를 메모한 종이를 통해 실제 그 주소로 이동해야 실체를 만나고, 무언가를 수정할 수 있다.
int a = 0;
int* addressOfa = &a; // a의 주소를 까서 addressOfa에 저장함.
addressOfa = 10; // 이런다고 해도 a의 값은 변함이 없음.
int* addressOfa = &a; // a의 주소를 까서 addressOfa에 저장함.
(*addressOfa) = 10; // 주소로 이동하기 연산자를 통해 addressOfa가 가리키는 곳에 이동해서 10을 넣으면, 이제 a의 값이 10이됨
(5) 역참조 *
선언용 *가 주소를 받는 변수 타입(포인터 타입)을 선언하는 데 사용되는 반면에, 이미 선언된 변수에 사용되는 * 연산자는, 해당 주소로 이동하는 연산자다. 짧게 표현할 때 ->를 쓰기도 한다.
int a = 10;
int* addressOfa = &a; // a의 주소를 까서 addressOfa에 저장함.
print(addressOfa); // a의 주소가나옴
print(*addressOfa); // a의 주소로 이동하여 찾은 값 10이 나옴