참조
2025. 3. 28. 08:43ㆍProgramming Languages/C++
6.4 참조
6.4.1 참조의 기본 개념
참조는 기존 변수의 별칭(alias)으로, 변수와 동일한 메모리 위치를 가리킵니다:
#include <iostream>
int main() {
int num = 42;
int& ref = num; // num에 대한 참조 선언
std::cout << "num의 값: " << num << std::endl;
std::cout << "ref의 값: " << ref << std::endl;
// 참조를 통한 값 변경
ref = 100;
std::cout << "ref 변경 후 num의 값: " << num << std::endl;
// 원본 변수 값 변경
num = 200;
std::cout << "num 변경 후 ref의 값: " << ref << std::endl;
return 0;
}
6.4.2 참조와 포인터의 차이점
참조와 포인터는 모두 간접 참조 메커니즘을 제공하지만, 몇 가지 중요한 차이점이 있습니다:
#include <iostream>
int main() {
int num = 42;
// 포인터
int* ptr = #
// 참조
int& ref = num;
std::cout << "원본 값: " << num << std::endl;
// 포인터를 통한 값 변경
*ptr = 100;
std::cout << "포인터 변경 후: " << num << std::endl;
// 참조를 통한 값 변경
ref = 200;
std::cout << "참조 변경 후: " << num << std::endl;
// 포인터 재할당
int num2 = 50;
ptr = &num2;
std::cout << "포인터가 가리키는 새 값: " << *ptr << std::endl;
std::cout << "원래 변수: " << num << std::endl;
// 참조 재할당 불가능
// ref = num2; // 새 참조 생성이 아니라 값만 복사됨
ref = num2;
std::cout << "참조 '재할당' 후 원래 변수: " << num << std::endl; // num도 50이 됨
return 0;
}
주요 차이점:
- 참조는 반드시 초기화되어야 하며, null이 될 수 없습니다.
- 참조는 한 번 초기화되면 다른 변수를 참조하도록 변경할 수 없습니다.
- 참조는 항상 유효한 객체를 가리켜야 합니다.
- 포인터는 산술 연산이 가능하지만, 참조는 불가능합니다.
- 포인터는 명시적 역참조(*)가 필요하지만, 참조는 암시적으로 역참조됩니다.
6.4.3 함수 매개변수로서의 참조
참조는 함수 호출 시 값 복사 없이 변수를 전달하는 효율적인 방법을 제공합니다:
#include <iostream>
#include <string>
// 값에 의한 전달 (복사)
void incrementByValue(int num) {
num++; // 매개변수 복사본 수정
}
// 참조에 의한 전달
void incrementByReference(int& num) {
num++; // 원본 변수 직접 수정
}
// 포인터에 의한 전달
void incrementByPointer(int* numPtr) {
if (numPtr) { // null 체크 필요
(*numPtr)++; // 원본 변수 직접 수정
}
}
// 상수 참조 (읽기 전용)
void printInfo(const std::string& str) {
// str = "변경"; // 컴파일 오류: 상수 참조는 수정 불가
std::cout << "문자열: " << str << ", 길이: " << str.length() << std::endl;
}
int main() {
int a = 5, b = 10, c = 15;
incrementByValue(a);
std::cout << "값 전달 후 a: " << a << std::endl; // 5 (변경 없음)
incrementByReference(b);
std::cout << "참조 전달 후 b: " << b << std::endl; // 11 (증가됨)
incrementByPointer(&c);
std::cout << "포인터 전달 후 c: " << c << std::endl; // 16 (증가됨)
// 큰 객체의 효율적인 전달
std::string longString = "이것은 아주 긴 문자열로, 복사하면 비효율적입니다.";
printInfo(longString); // 값 복사 없이 참조로 전달
return 0;
}
6.4.4 반환 값으로서의 참조
함수가 참조를 반환할 수도 있습니다:
#include <iostream>
#include <string>
// 배열 요소에 대한 참조 반환
int& getElement(int arr[], int index) {
return arr[index];
}
// 클래스 멤버에 대한 참조 반환
class Person {
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
std::string& getName() { return name; }
int& getAge() { return age; }
void display() const {
std::cout << "이름: " << name << ", 나이: " << age << std::endl;
}
};
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// 함수가 반환한 참조를 통해 배열 요소 수정
getElement(numbers, 2) = 300;
std::cout << "numbers[2]: " << numbers[2] << std::endl; // 300
// 클래스 참조 반환 활용
Person person("홍길동", 25);
person.display();
// getName()이 반환한 참조를 통해 이름 변경
person.getName() = "김철수";
// getAge()가 반환한 참조를 통해 나이 변경
person.getAge() = 30;
person.display(); // 변경된 정보 출력
return 0;
}
주의사항: 지역 변수에 대한 참조를 반환하면 안 됩니다(댕글링 참조 발생):
int& badFunction() {
int localVar = 42;
return localVar; // 위험! 함수가 종료되면 localVar은 소멸됨
}
6.4.5 우측값 참조(C++11)
C++11에서는 "우측값 참조(rvalue reference)"가 도입되어 이동 의미론(move semantics)과 완벽한 전달(perfect forwarding)을 지원합니다:
#include <iostream>
#include <string>
#include <utility> // std::move
// 우측값 참조를 받는 함수
void processRvalueRef(std::string&& str) {
std::cout << "우측값 참조로 받음: " << str << std::endl;
// str은 이제 함수 내에서 좌측값
}
// 좌측값 참조와 우측값 참조 오버로딩
void process(const std::string& str) {
std::cout << "좌측값 참조로 처리: " << str << std::endl;
}
void process(std::string&& str) {
std::cout << "우측값 참조로 처리: " << str << std::endl;
}
int main() {
// 좌측값 (이름이 있는 객체)
std::string hello = "Hello, World!";
// 좌측값 참조
std::string& lref = hello;
// 우측값 참조는 임시 객체를 참조할 수 있음
std::string&& rref = std::string("Temporary String");
std::cout << "lref: " << lref << std::endl;
std::cout << "rref: " << rref << std::endl;
// 오버로딩된 함수 호출
process(hello); // 좌측값 버전 호출
process(std::string("Temporary")); // 우측값 버전 호출
process(std::move(hello)); // 우측값 버전 호출 (좌측값을 우측값으로 변환)
// std::move 이후 hello는 "이동된" 상태가 됨 (내용이 정의되지 않음)
std::cout << "이동 후 hello: " << hello << std::endl;
return 0;
}
우측값 참조의 주요 용도:
- 이동 의미론(Move Semantics): 객체의 리소스를 복사 대신 "이동"시켜 성능 향상
- 완벽한 전달(Perfect Forwarding): 템플릿에서 인수의 값 범주(좌측값/우측값)를 그대로 유지하며 전달
- 이동 생성자와 이동 대입 연산자 구현
'Programming Languages > C++' 카테고리의 다른 글
| 챕터6. 실습 문제 (0) | 2025.03.28 |
|---|---|
| 고급 포인터 주제 (0) | 2025.03.28 |
| 동적 메모리 할당 (0) | 2025.03.28 |
| 포인터의 기본 (0) | 2025.03.28 |
| 포인터와 참조 (0) | 2025.03.28 |