프로그램/C,C++

[C++ 기본 2] 1. reference

naudhizb 2022. 1. 29. 23:31
반응형

C에서 C++로 넘어오게 되면서 큰 차이를 느끼는 문법 중의 하나가 바로 reference의 존재이다.

reference의 경우 변수에 대한 별명을 부여하여 사용은 변수처럼, 동작은 포인터처럼 동작 가능한 C++에서 추가된 변수 타입이다.

물론 이를 통하여 편리함을 얻게 되었지만, 문법을 잘 아는 것이 아니라면 call by value인지 call by reference인지 헷갈리는 경우가 있다.

// 1_레퍼런스.cpp    41 page

#include <iostream>
// 핵심 1. 기존 변수(메모리)에 새로운 이름을 부여 하는 것
//     2. 레퍼런스 변수 선언시 반드시 초기값이 있어야 합니다.
int main()
{
    int n1 = 10;
    int n2 = n1;
    int* p1 = &n1;

    int& r1 = n1; // C++ 에서 추가한 새로운 타입

    r1 = 30;

    std::cout << n1  << std::endl; // 30
    // 결국 r1의 주소와 n1의 주소는 동일 합니다.
    std::cout << &r1 << std::endl;
    std::cout << &n1 << std::endl;

    // 포인터와 레퍼런스는 모두 기존 메모리를 가리킬수 있습니다.
    // null 포인터는 존재 할수 있지만 null 레퍼런스는 존재 할수
    // 없습니다
    int& r2; // error
    int* p2 = nullptr; // ok

    
}
// 1_레퍼런스2
#include <iostream>
// 43 page

// 포인터와 레퍼런스의 용도는 유사합니다.
// 1. "자동 dereference 되는 포인터" 가 레퍼런스 입니다
// 2. 포인터는 if ( p != nullptr ) 이 필요 하지만
//    레퍼런스는 필요 없습니다. - 포인터 보다 안전.

// C++ 에서는 포인터 보다 레퍼런스를 더 많이 사용합니다.

void inc1(int n)  { ++n; }
void inc2(int* p) { ++(*p); }
void inc3(int& r) { ++r; }

int main()
{
    int a = 1, b = 1, c = 1;

    inc1(a);        // call by value. 원본 수정 안됨
    inc2(&b);        // call by pointer. 원본 수정 가능
    inc3(c);        // call by reference. 원본 수정 가능

    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;

    // 사용자 입력을 받아 올때
    scanf("%d", &a);  // 2번째 인자가 포인터 입니다.
    cin >> a;          // >> 함수의 인자가 참조 입니다.
}

위의 설명과 같이 reference 타입은 "자동으로 dereference 되는 포인터" 이다 때문에 dereference 되지 않는 특성(i.e. nullptr)이 존재하지 않도록 컴파일러가 막게 된다.

// 1_레퍼런스3. 45 page
struct BigData
{
    int buff[1024];
};
// call by value 의 특징 : 값을 수정하지 않겠다는 의도.
//                       하지만, 복사본이 생성되므로
//                        메모리 부담이 크다.
//void foo(BigData n)
// 핵심 : call by value 대신 const &가 좋다.!
void foo(const BigData& n)
{
    //n.buff[0] = 10; // error. n은 상수
}
int main()
{
    //int x = 100;
    BigData x;

    // 어떤 함수에서 인자의 값을 절대 수정하면 안된다면
    foo(x);

}

// C++에서 함수 인자 만들기
// 1. 인자의 값을 변경하고 싶을때 - 포인터 또는 레퍼런스
void foo(int* p) {}
void foo(int& r) {}

// 2. 인자의 값을 변경하지 않을때
// A. 인자가 user type 일때 - 일반적으로 크기가 작지 않으므로
//                    call by value 보다는 const 참조가 좋다
void foo(const UserType& t) {}

// B. 인자가 primitive type 일때
void foo(int t) {}

함수의 인자로 대형의 데이터를 전달할 때 값을 변경하지 않기를 원하는 경우 const referencetype을 사용할 수 있다.

// 1_레퍼런스4.   100 page

// 핵심 : 함수가 참조를 반환하면 등호에 왼쪽에 놓을수 있습니다.

int x = 0;
int foo()  { return x; } // 값 타입 반환 : 메모리에 있는 값 반환
int& goo() { return x; } // 참조 반환 : 값이 아닌 메모리 반환

// 주의 : 절대 지역변수를 참조 반환 하면 안됩니다.
int& hoo() 
{
    int n = 0; 
    return n; 
}

int main()
{
    int n1 = 0, n2 = 0;
    n2 = n1; // n1이 오른쪽에 있다. 컴파일러는 n1의 값을 
             // 가져오는 코드 생성
    n1 = 10; // n1이 왼쪽에 있다. 컴파일러는 n1의 주소를
             // 사용하는 코드 생성

    foo() = 20; // error
    goo() = 20; // ok.. 
}

// google 에서 C++ core guideline
// 2번째 링크

자동 dereference 되는 특성을 가지고 있기 때문에 등호의 왼쪽에도 reference 타입이 쓰일 수 있다.

본래 *ptr = data; 에서 레퍼런스 타입은 자동으로 디레퍼런싱을 수행하기 때문에
ref_func() = data; 와 같은 동작이 성립한다.

반응형