[C++] 참조자와 포인터 : 별명과 주소의 차이

[C++] 참조자와 포인터 : 별명과 주소의 차이

개요

안녕하세요. 이번 글에서는 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 가능, *로 역참조.
  • 함수에 원본을 넘겨 바꾸려면 참조나 포인터를 쓴다 (값은 복사본).
  • 바뀌지 않는 확실한 대상이면 참조, 없을 수도/바뀔 수도 있으면 포인터.

참조는 안전한 별명, 포인터는 유연한 주소라고 기억하면 둘의 선택이 쉬워집니다. 이상으로 참조자와 포인터의 차이에 대해 간단히 알아보았습니다.

참고