개요
안녕하세요. 이번 글에서는 C++의 참조자(reference)와 포인터(pointer)의 차이에 대해 알아보겠습니다. 둘 다 “다른 변수를 가리키는” 역할을 하지만, 참조자는 변수의 또 다른 이름(별명)이고 포인터는 변수의 주소를 담는 변수라는 점에서 다릅니다.
참조자 — 변수의 별명
참조자는 &를 붙여 선언하며, 기존 변수의 또 다른 이름이 됩니다. 선언할 때 반드시 초기화해야 하고, 한 번 정하면 다른 변수를 가리키도록 바꿀 수 없습니다.
int a = 10;
int& ref = a; // ref는 a의 별명 (반드시 초기화)
ref = 20; // a를 바꾸는 것과 동일
std::cout << a; // 20
// int& r; // 에러: 초기화 없이 선언 불가
ref와 a는 완전히 같은 저장 공간을 가리키므로, ref를 바꾸면 a도 바뀝니다. 별명이라 *같은 역참조 기호도 필요 없습니다.
포인터 — 주소를 담는 변수
포인터는 *로 선언하고, 변수의 주소를 담습니다. 주소는 &로 얻고, 그 주소가 가리키는 값에 접근할 때는 *(역참조)를 씁니다. 참조자와 달리 나중에 다른 대상을 가리키도록 바꿀 수 있고, 아무것도 안 가리키는 nullptr도 가능합니다.
int a = 10, b = 99;
int* p = &a; // p는 a의 주소를 담음
*p = 20; // 역참조: a를 20으로
std::cout << a; // 20
p = &b; // 다른 변수를 가리키도록 변경 가능
p = nullptr; // 아무것도 안 가리킴
참조자 vs 포인터
| 구분 | 참조자 (&) | 포인터 (*) |
|---|---|---|
| 의미 | 변수의 별명 | 주소를 담는 변수 |
| 초기화 | 선언 시 필수 | 나중에 해도 됨 |
| 대상 변경 | 불가 (고정) | 가능 (재지정) |
| nullptr | 불가 | 가능 |
| 접근 | 그냥 이름처럼 | *로 역참조 |
정리하면 참조자는 “한 번 정하면 바뀌지 않는 안전한 별명”, 포인터는 “자유롭게 바꿀 수 있지만 조심해서 다뤄야 하는 주소”입니다.
함수 인자로 넘길 때
가장 많이 쓰는 곳이 함수 인자입니다. 그냥 값으로 넘기면 복사본이 전달되어 원본이 안 바뀌지만, 참조나 포인터로 넘기면 원본을 직접 바꿀 수 있습니다.
void addOne(int x) { x++; } // 값: 복사본만 변경
void addOne(int& x) { x++; } // 참조: 원본 변경
void addOne(int* x) { (*x)++; } // 포인터: 원본 변경
int n = 5;
addOne(n); // 참조 버전 → n = 6
addOne(&n); // 포인터 버전 → 주소를 넘김
참조로 넘기면 호출 쪽이 addOne(n)처럼 깔끔하고, 포인터는 addOne(&n)처럼 주소를 명시합니다. 큰 객체를 복사 없이 읽기만 할 때는 const 참조(const T&)를 자주 씁니다.
언제 무엇을 쓰나
- 참조자: 반드시 유효한 대상이 있고, 바꿀 필요 없을 때. 함수 인자·반환에 깔끔하다.
- 포인터: 가리키는 대상이 없을 수도 있거나(nullptr), 중간에 다른 대상으로 바꿔야 할 때.
- 현대 C++에서는 가능하면 참조를 먼저 고려하고, 꼭 필요할 때 포인터(또는 스마트 포인터)를 쓴다.
정리
- 참조자는 변수의 별명 — 선언 시 초기화 필수, 대상 변경 불가, nullptr 없음.
- 포인터는 주소를 담는 변수 — 재지정 가능, nullptr 가능, *로 역참조.
- 함수에 원본을 넘겨 바꾸려면 참조나 포인터를 쓴다 (값은 복사본).
- 바뀌지 않는 확실한 대상이면 참조, 없을 수도/바뀔 수도 있으면 포인터.
참조는 안전한 별명, 포인터는 유연한 주소라고 기억하면 둘의 선택이 쉬워집니다. 이상으로 참조자와 포인터의 차이에 대해 간단히 알아보았습니다.