int iNum = 10;

// int 형 포인터 변수
int* pPtr = &iNum;

// iNum의 레퍼런스인 rRef
int& rRef = iNum;

포인터 = 메모리의 주소를 가지고 있는 변수

참조자 = 자신이 참조하고 있는 변수를 대신할 수 있는 또 하나의 이름

 

무슨 차이인가 하면

포인터 변수는 iNum을 가리키는 주소를 담고있는 그릇이고,

레퍼런스는 별도의 그릇(공간)이 생성되지않고 iNum 이라는 이름 대신 다른 이름(rRef)을 사용할 수 있다는 뜻 이다.

따라서 포인터는 별도의 메모리 공간을 소모 하고 (64비트 기준으로 8byte)

레퍼런스는 같은 메모리 공간을 참조 하므로 메모리 공간을 소모하지 않는다.

int a = 5;
int& b = a;
int* c = &a;

cout << a << endl;   // 5
cout << b << endl;   // 5
cout << *c << endl;  // 5
cout << &a << endl;  // 00000068A30FF5D4
cout << &b << endl;  // 00000068A30FF5D4
cout << c << endl;   // 00000068A30FF5D4

 

 

또 다른 차이점으로 포인터는 nullptr 초기화 할 수 있지만, 레퍼런스는 선언과 동시에 초기화가 이루어져야 한다.

int iNum = 10;

// int 형 포인터 변수
int* pPtr = nullptr;

// iNum의 레퍼런스인 rRef 
int& rRef = nullptr;  // error

레퍼런스가 주소가 없는 0x0 을 가리키면 안되므로

 

 

#include <iostream>

using namespace std;

int& AllRefTest(int &ref) {
    ++ref;
    return ref;
}

int ParamRefTest(int &ref) {
    ++ref;
    return ref;
}

int NonRefTest(int ref) {
    ++ref;
    return ref;
}

int main() {
    int AllRefNum = 10;
    int ParamRefNum = 10;
    int NonRefNum = 10;
 
    int &num1 = AllRefTest(AllRefNum);
    int num2 = ParamRefTest(ParamRefNum);
    int num3 = NonRefTest(NonRefNum);

    ++num1;
    ++num2;
    ++num3;

    cout << "AllRefNum: " << AllRefNum << endl;
    cout << "ParamRefNum: " << ParamRefNum << endl;
    cout << "NonRefNum: " << NonRefNum << endl;
    cout << "num1: " << num1 << endl;
    cout << "num2: " << num2 << endl;
    cout << "num3: " << num3 << endl;
}

 

처음에 각 변수에 10이라는 값이 할당 된다.

그리고 각 함수에서 받은 Ref 값은 다음과 같이 가리킨다.

 

또한 각 함수에서 ++Ref를 해주면 다음과 같아진다.

 

그리고 각 함수에서 값을 반환하면 다음과 같다. (ref 는 함수 파라미터이므로 함수 종료 후 사라짐)

마지막으로 각 num들을 ++ 해주면 다음과 같아진다.

 

AllRefNum: 12
ParamRefNum: 11
NonRefNum: 10
num1: 12
num2: 12
num3: 12
cout << "AllRefNum: " << &AllRefNum << endl;
int &num1 = AllRefTest(AllRefNum);
cout << "num1: " << &num1 << endl;


cout << "ParamRefNum: " << &ParamRefNum << endl;
int num2 = ParamRefTest(ParamRefNum);
cout << "num2: " << &num2 << endl;


cout << "NonRefNum: " << &NonRefNum << endl;
int num3 = NonRefTest(NonRefNum);
cout << "num3: " << &num3 << endl;


// ---Output---

// AllRefNum: 0x61fe14
// AllRefTest: 0x61fe14
// num1: 0x61fe14

// ParamRefNum: 0x61fe10
// ParamRefTest: 0x61fe10
// num2: 0x61fe08

// NonRefNum: 0x61fe0c
// NonRefTest: 0x61fde0
// num3: 0x61fe04

 

 

함수 내부에서 사용되는 지역변수는 함수가 끝나면 스택 메모리 영역에서 사라진다.

그럼 만약에 함수내부에서 사용되는 지역변수를 참조 리턴값으로 반환하면 어떻게 될 까?

int& RefTest(int ref) {
    int num = ref;
    return num;
}

위 함수가 반환될 때 소멸할 num 에 대한 참조를 반환한다.

이것은 호출자가 쓰레기에 대한 참조를 받는다는 것을 의미한다.

다행히도, 컴파일러는 이것을 시도하면 에러를 띄어준다.

 

 

 

참조로 반환을 사용해야 하는 경우:

  • 참조 매개 변수를 반환할 때
  • 함수에 전달된 배열의 요소를 반환할 때
  • 함수의 끝에서 소멸하지 않는 구조체나 클래스를 반환할 때

참조로 반환을 사용해야 하지 않아야 하는 경우:

  • 함수 내에서 선언된 (지역)변수를 반환할 때 (값으로 반환 사용)
  • 기본 배열이나 포인터 값을 반환할 때 (주소로 반환 사용)

 

Use references when you can, and pointers when you have to
사용할 수 있다면 참조자를, 어쩔 수 없다면 포인터를 써라

'프로그래밍 > C++' 카테고리의 다른 글

연산자 오버로딩  (1) 2023.08.13
전방 선언  (0) 2023.05.21
멤버 이니셜라이저 사용하는 이유  (0) 2023.01.28
C++ 람다 예제  (0) 2023.01.02
Thread  (0) 2022.06.15

+ Recent posts