[C++] 참조자(레퍼런스)
위 사이트에서 공부한 내용 정리입니다
참조자란
C++에서 다른 변수나 상수를 가리키는 방법으로 또 다른 방식을 제공하는데, 이를 참조자라고 부른다
#include <iostream>
int main() {
int a = 3;
int& another_a = a;
another_a = 5;
std::cout << "a : " << a << std::endl;
std::cout << "another_a : " << another_a << std::endl;
return 0;
}
해당 코드에서 another_a는 a의 참조자라고 한다
another_a는 a의 또 다른 이름이라고 컴파일러에게 알려주는 것이다
위처럼 int형 변수의 참조자를 만들고 싶을 땐 int&, double의 참조자를 만들고 싶을 땐 double&로 선언하면 된다
int*와 같은 포인터 타입의 참조자를 만드려면 *int&**로 쓰면 된다
포인터와 차이점
참조자는 포인터랑 비슷한 개념이다. 포인터 역시 다른 변수의 주소값을 보관하고 변수에 간접적으로 접근할 수 있기 때문이다. 하지만 레퍼런스와 차이점이 있다
1. 레퍼런스는 반드시 처음에 누구의 별명이 될 것인지 지정해야 한다
레퍼런스는 정의 시에 반드시 누구의 별명인지 명시해야한다
int& another_a; // 불가능
int* p // 가능
2. 레퍼런스가 한 번 별명이 되면 절대로 다른 이의 별명이 될 수 없다
int a = 10;
int &another_a = a; // another_a 는 이제 a 의 참조자!
int b = 3;
another_a = b; // ??
중요) 여기서 another_a는 b의 참조자가 아니라 a에 3을 대입하라는 것이랑 똑같은 의미다
3. 레퍼런스는 메모리 상에 존재하지 않을 수 도 있다.
// 포인터
int a = 10;
int* p = &a;
// 레퍼런스
int a = 10;
int &another_a = a;
포인터의 경우 p는 8바이트를 차지하지만 레퍼런스의 경우 another_a가 쓰이는 부분에 a로 교체만 하면 돼서 메모리 상에 존재하지 않아도 된다
상수에 대한 참조자
#include <iostream>
int main() {
int &ref = 4;
std::cout << ref << std::endl;
}
일반적으로 상수 값 자체는 리터럴이기 때문에 위의 코드는 컴파일 에러가 발생한다
const int &ref = 4;
하지만 상수 참조자로 선언한다면 리터럴도 참조할 수 있다
레퍼런스의 배열과 배열의 레퍼런스
// 레퍼런스의 배열
int a, b;
int& arr[2] = {a, b};
// 배열의 레퍼런스
int main() {
int arr[3] = {1, 2, 3};
int(&ref)[3] = arr;
ref[0] = 2;
ref[1] = 3;
ref[2] = 1;
std::cout << arr[0] << arr[1] << arr[2] << std::endl;
return 0;
}
솔직히 내가 이해하기로는 레퍼런스의 배열이 아래 같고, 배열의 레퍼런스가 위에 같긴 하지만
이해한 바로는 배열 전체의 레퍼런스 선언은 불가능하고, 각 레퍼런스 변수들의 배열이 가능하다라고 이해했다
int arr[3][2] = {1, 2, 3, 4, 5, 6};
int (&ref)[3][2] = arr;
2차원 배열에서도 마찬가지로 위의 코드처럼 사용할 수 있다
레퍼런스를 리턴하는 함수
int& function() {
int a = 2;
return a;
}
int main() {
int b = function();
b = 3;
return 0;
}
위 코드는 컴파일 오류는 아니지만 런타임 오류가 발생한다
왜냐면 function의 return타입은 int&인데, function안에 a는 함수의 리턴과 함께 사라지기 때문이다
int& ref = a;
// 근데 a 가 사라짐
int b = ref;
이런 느낌이라고 한다
이처럼 레퍼런스는 있는데 원래 참조하던 것이 사라진 레퍼런스를 댕글링 레퍼런스(Dangling reference)라고 한다
int& function(int& a) {
a = 5;
return a;
}
int main() {
int b = 2;
int c = function(b);
return 0;
}
이 코드는 a가 외부 변수를 받아오고 그 변수를 리턴하기 때문에 정상적으로 동작한다
참조자를 리턴하는 장점
레퍼런스를 리턴하게 된다면 레퍼런스가 참조하는 타입의 크기와 상관없이 딱 한번의 주소값 복사로 전달이 끝나게 된다
참조자가 아닌 값을 리턴하는 함수를 참조자로 받
int function() {
int a = 5;
return a;
}
int main() {
int& c = function();
return 0;
}
(말부터 뭔가 어렵다..)
첫번째 케이스와 비슷하게 function이 return이 끝난 후 사라지는 값이기 때문에 컴파일 에러가 발생한다고 이해했다
const int& c = function();
하지만 이런식으로 const 참조자로 받으면 컴파일이 되고 정상적으로 리턴값도 출력이 된다
상수 레퍼런스로 리턴값을 받게되면 해당 리턴값의 생명이 연장된다.
그리고 그 연장되는 기간은 레퍼런스가 사라질 때 까지다

