참조
              
          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 |