
10. 레퍼런스(&)와 포인터(*)
개요
레퍼런스(reference)와 포인터(pointer)는 C++에서 메모리에 있는 기존 변수에 간접적으로 접근하는 방법을 제공합니다. 포인터는 변수의 메모리 주소를 저장하는 변수이고, 레퍼런스는 변수의 또 다른 이름(별칭)입니다.
C++ 버전별 주요 키워드 도입 시기
- C++98: 포인터는 C에서 계승되었고, 레퍼런스(L-value 레퍼런스)는 C++ 초기부터 도입되어 객체를 효율적으로 전달하는 핵심 기능으로 사용되었습니다.
- C++11: R-value 레퍼런스(
&&)가 도입되어, 임시 객체를 효율적으로 처리하는 이동 시맨틱(move semantics)과 완벽한 전달(perfect forwarding)이 가능해졌습니다. 또한, 0이나NULL대신nullptr키워드를 사용하여 널 포인터를 명확하게 표현하는 것이 권장됩니다.
내용 설명
차이점
| 구분 | 포인터 (Pointer) | 레퍼런스 (Reference) |
|---|---|---|
| 선언 | int* p = &var; |
int& ref = var; |
| 초기화 | 선언 후 다른 변수의 주소를 할당할 수 있음 | 선언 시 반드시 초기화해야 함 |
| 재할당 | 가리키는 대상을 변경할 수 있음 | 다른 변수를 가리키도록 변경할 수 없음 |
| Null | nullptr를 가리킬 수 있음 (아무것도 가리키지 않음) |
null이 될 수 없음 (항상 유효한 객체를 참조) |
| 접근 | 역참조 연산자(*)를 사용 (*p) |
변수 이름처럼 직접 사용 (ref) |
| 본질 | 주소를 저장하는 별도의 변수 | 기존 변수의 또 다른 이름 (별칭) |
함수 인자로서의 활용
- 포인터 전달(Pass-by-pointer): 함수의 인자로 변수의 주소를 전달합니다. 함수 내에서 포인터를 통해 원본 변수를 수정할 수 있습니다.
nullptr전달이 가능하므로, 함수 내에서 유효성 검사가 필요할 수 있습니다. - 레퍼런스 전달(Pass-by-reference): 함수에 변수 자체를 전달하는 것처럼 보이지만, 실제로는 원본 변수의 별칭이 전달됩니다. 함수 내에서 레퍼런스를 통해 원본 변수를 수정할 수 있으며,
null이 아니므로 더 안전하고 구문이 간결합니다.
함수 반환 값으로서의 활용
- 값 반환(Return-by-value): 객체의 복사본을 반환합니다. 원본 데이터는 변경되지 않지만, 객체가 클 경우 복사 비용이 발생할 수 있습니다.
- 레퍼런스 반환(Return-by-reference): 객체의 레퍼런스를 반환하여 복사를 피하고, 반환된 레퍼런스를 통해 원본 객체를 직접 수정할 수 있습니다. 단, 함수가 종료된 후에도 존재하는 전역 변수나 정적 변수, 동적 할당된 객체를 반환해야 하며, 지역 변수의 레퍼런스를 반환하면 안 됩니다 (댕글링 레퍼런스).
예제 코드
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
};
// Pass-by-reference
void updateAgeByRef(Person& p, int newAge) {
p.age = newAge; // 원본 객체의 age가 변경됨
}
// Pass-by-pointer
void updateAgeByPtr(Person* p, int newAge) {
if (p != nullptr) { // 항상 null 체크를 하는 것이 안전
p->age = newAge; // 원본 객체의 age가 변경됨
}
}
// 전역 변수 (레퍼런스 반환 예제를 위해)
Person globalPerson = {"Global", 99};
// Return-by-reference
Person& getGlobalPerson() {
return globalPerson; // 전역 변수의 레퍼런스 반환
}
int main() {
Person p1 = {"Alice", 30};
Person p2 = {"Bob", 25};
std::cout << "--- Initial State ---" << std::endl;
std::cout << p1.name << " is " << p1.age << " years old." << std::endl;
std::cout << p2.name << " is " << p2.age << " years old." << std::endl;
// 레퍼런스 및 포인터로 함수 호출
updateAgeByRef(p1, 31);
updateAgeByPtr(&p2, 26);
std::cout << "\n--- After Update ---" << std::endl;
std::cout << p1.name << " is now " << p1.age << " years old." << std::endl;
std::cout << p2.name << " is now " << p2.age << " years old." << std::endl;
// 레퍼런스 반환 값 사용
std::cout << "\n--- Return-by-reference ---" << std::endl;
std::cout << "Global person's age: " << getGlobalPerson().age << std::endl;
// 반환된 레퍼런스를 통해 원본 수정
getGlobalPerson().age = 100;
std::cout << "Global person's new age: " << globalPerson.age << std::endl;
return 0;
}
실행 결과
--- Initial State ---
Alice is 30 years old.
Bob is 25 years old.
--- After Update ---
Alice is now 31 years old.
Bob is now 26 years old.
--- Return-by-reference ---
Global person's age: 99
Global person's new age: 100
활용팁
- 간결성과 안전성: 함수 인자로 객체를 전달할 때,
null이 허용되지 않고 구문이 간결한 레퍼런스를 우선적으로 고려하는 것이 좋습니다. - 선택적 인자: 인자가 선택적이어서
nullptr를 전달해야 하는 경우 포인터를 사용합니다. - 효율성: 큰 객체를 함수에 전달할 때 값에 의한 전달(pass-by-value)은 비싼 복사를 유발하므로,
const레퍼런스를 사용하여 복사를 피하고 원본 데이터의 수정을 방지하는 것이 매우 효율적입니다. (예:void printPerson(const Person& p);)
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
| Before Classic C++ : struct (1) | 2025.10.31 |
|---|---|
| Before Classic C++ : Preprocessor (0) | 2025.10.30 |
| Before Classic C++ : Bitwise Operators (0) | 2025.10.29 |
| Before Classic C++ : Conditional Operator (1) | 2025.10.28 |
| Before Classic C++ : enum (0) | 2025.10.27 |
| Before Classic C++ : Comma Operator (0) | 2025.10.26 |
| Before Classic C++ : goto (1) | 2025.10.25 |
| Before Classic C++ : continue (0) | 2025.10.24 |