C++을 공부하며 반드시 마주치는 두 개의 큰 산, 포인터와 레퍼런스를 만났다. 처음에는 '그냥 변수 쓰면 되지 왜 이렇게 복잡한 게 필요할까?' 싶었지만, 그 이유를 알고 나니 C++의 깊은 매력을 느낄 수 있었다. 단순히 값을 복사하는 것의 비용 문제를 해결하고, 메모리를 효율적으로 관리하기 위해 주소를 직접 다루는 포인터와, 변수에 새로운 이름(별명)을 붙여주는 레퍼런스의 기본 개념을 확실히 정리해 보았다. 🧐
int b = a;와 같이 변수를 복사하면, 컴퓨터는 a가 가진 값을 그대로 복사해서 b라는 새로운 메모리 공간에 넣어준다. int(4바이트)처럼 작은 변수는 문제가 없었지만, 만약 수백만 개의 데이터를 가진 배열을 복사한다면 어떨지 생각해 보았다. 😱
이는 마치 4,000장짜리 책을 한 글자씩 전부 베껴 쓰는 것과 같았다. 시간도, 공간도 엄청나게 낭비되는 것이다. C++에서는 이런 비효율을 막기 위해 "값을 통째로 복사하지 말고, 값이 어디에 있는지 '주소'만 알려주자!"라는 아이디어를 사용했다.
포인터는 바로 이 '주소'를 저장하기 위해 태어난 특별한 변수이다.
타입* 변수명; 형태로 선언한다. *는 이 변수가 포인터임을 나타낸다. (예: int* ptr;)& (주소 연산자): 변수 앞에 붙여 그 변수의 메모리 주소를 가져온다. (예: &myVar)* (역참조 연산자): 포인터 변수 앞에 붙여, 그 포인터가 가리키는 주소에 실제로 저장된 값을 가져온다. (예: *ptr)위 그림처럼, 포인터 변수 ptr은 x의 값인 3을 직접 담는 게 아니라, x가 위치한 메모리 주소 200을 담고 있다. 그리고 *ptr을 통해 비로소 값 3에 접근할 수 있다.
레퍼런스는 이미 존재하는 변수에 새로운 이름(별명)을 붙여주는 것이다.
타입& 변수명 = 원본변수; 형태로 선언하며, 선언과 동시에 반드시 초기화해야 한다. (예: int& ref = myVar;)nullptr을 가질 수 없으며, 반드시 실제 변수를 가리켜야 한다.*나 & 같은 복잡한 연산자가 필요 없어 편리하다.| 구분 | 포인터 (Pointer) | 레퍼런스 (Reference) |
|---|---|---|
| 본질 | 주소를 저장하는 독립된 변수 | 기존 변수의 별명(Alias) |
| 초기화 | 선언만 하고 나중에 값을 할당할 수 있음 | 선언과 동시에 반드시 초기화해야 함 |
| 재할당 | 가리키는 대상을 다른 변수로 변경 가능 | 한 번 가리킨 대상을 변경할 수 없음 |
| Null 가능성 | nullptr을 가질 수 있음 (아무것도 가리키지 않음) | 불가능 (항상 유효한 대상을 가리켜야 함) |
| 사용법 | * 연산자로 값에 접근 (*ptr) | 일반 변수처럼 사용 (ref) |
#include <iostream>
int main() {
// 1. 원본 변수 선언
int original_value = 10;
// 2. 포인터 선언 및 사용
int* pointer_var; // 포인터 변수 선언
pointer_var = &original_value; // original_value의 '주소'를 저장
// 3. 레퍼런스 선언 및 사용
// 레퍼런스는 선언과 동시에 반드시 초기화해야 합니다.
int& reference_var = original_value;
// 값 출력해보기
std::cout << "--- 값 확인 ---" << std::endl;
std::cout << "원본 값: " << original_value << std::endl;
std::cout << "포인터를 통해 접근한 값: " << *pointer_var << std::endl;
std::cout << "레퍼런스를 통해 접근한 값: " << reference_var << std::endl;
// 주소 값 출력해보기
std::cout << "\n--- 주소 확인 ---" << std::endl;
std::cout << "원본 변수의 주소: " << &original_value << std::endl;
std::cout << "포인터 변수가 저장하고 있는 값(주소): " << pointer_var << std::endl;
std::cout << "레퍼런스 변수의 주소: " << &reference_var << std::endl;
return 0;
}
int* ptr = 10; 이런 식이었다. 당연히 컴파일러가 화를 냈다. 😅 포인터는 '주소'만 먹는 편식쟁이라는 걸 기억해야 했다.int& ref; 나중에 초기화하려고 했는데, 레퍼런스는 선언과 동시에 짝을 맺어줘야 한다는 규칙을 몰라서 한참 헤맸다. 마치 태어나자마자 운명의 상대를 만나야 하는 것과 같았다. 😂| 개념 | 설명 | 비고 |
|---|---|---|
| 값 복사 | 변수의 실제 데이터를 복사하는 것. 데이터가 크면 비용이 비싸다. | int b = a; |
| 주소 | 데이터가 저장된 메모리의 고유한 위치 값. | &a |
| 포인터 | 변수의 '주소'를 값으로 가지는 특별한 변수. | int* ptr = &a; , *ptr로 값에 접근 |
| 레퍼런스 | 기존 변수에 붙여주는 '별명'. | int& ref = a;, ref로 값에 접근 (더 간편함) |