참조자 (레퍼런스 - reference)
다른 변수나 상수를 가리키는 방법 (별명을 붙여주는 것)
참조자를 사용하면 불필요한 &와 *를 사용하지 않아도 되기때문에 코드를 훨씬 간결하게 나타낼 수 있다.
자료형& 참조자명 = 변수명;
ex) int& another_a = a;
double& another_b = b;
int*& another_c = &c;
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;
}
컴파일 결과 a : 5, another_a : 5의 결과가 나온다.
int& another_a = a; 는 another_a는 a의 참조자이다 라고 선언한 것이다.
즉, another_a는 a 변수의 또 다른 이름이라고 컴파일러에게 알려주는 것이다.
another_a에 어떤 작업을 수행하든 a에 작업을 하는것과 마찬가지이다
레퍼런스의 특징
- 레퍼런스는 정의 시 반드시 누구의 별명인지를 명시해야 한다.
int& anohter_a; -> 불가능
int& another_a = a; -> 가능
- 레퍼런스가 한 번 별명이 되면 절대로 다른 이의 별명이 될 수 없다.
한 번 어떤 변수의 참조자가 되면 더이상 다른 변수를 참조할 수 없다
int a = 10;
int& another_a = a; // another_a는 a의 참조자 정의
int b = 3;
another_a = b; // 불가능, 에러 -> 단순히 a에 b의 값을 대입하라는 의미 (a = b와 같은 의미)
- 레퍼런스는 메모리 상에 존재하지 않을 수도 있다
레퍼런스는 변수의 별명이므로 굳이 레퍼런스를 위한 메모리 공간을 할당할 필요가 없다.
레퍼런스가 쓰이는 자리는 변수로 바꿔주면 되기 때문이다.
인자로 레퍼런스를 받는 함수
int change_val(int &p) {
p = 3;
return 0;
}
int main() {
int number = 5;
std::cout << number << std::endl;
change_val(number);
std::cout << number << std::endl;
}
레퍼런스 p는 change_val(number)이 호출될때 정의된다.
호출되는 순간 int &p = number; 가 실행된다고 생각하면 된다.
change_val(number);에서 포인터가 인자일떄와는 달리 &을 붙일 필요가 없다.
int x;
int& y = x; // x의 참조자로 y를 정의
int& z = y; // x의 참조자 y의 참조자로 z를 정의하는 의미가 아님
x의 참조자를 선언해라와 같은 의미가 되서 z역시 x의 참조자가 되는 것
따라서 y, z 모두 x의 참조자가 되는 것이다.
참조자의 참조자를 만드는 것은 문법상으로 금지되어있다.
상수에 대한 참조자
int main() {
int &ref = 4;
std::cout << ref << std::endl;
}
상수 값 자체는 리터럴이기 때문에 레퍼런스가 참조할 수 없다.
단, const int &ref = 4; 와 같이 상수 참조자로 선언한다면 리터럴도 참조할 수 있다.
const int &ref = 4;
int a = ref;
위의 코드는 a = 4; 와 동일한 의미인 것이다.
레퍼런스의 배열과 배열의 레퍼런스
int a, b;
int& arr[2] = {a, b};
위의 코드는 오류가 발생한다.
레퍼런스의 레퍼런스, 레퍼런스의 배열, 레퍼런스의 포인터는 문법상 금지이기 때문이다.
int arr[3] = {1, 2, 3};
int(&ref)[3] = arr;
ref가 arr을 참조하도록 한 코드이다. 즉 ref[0] ~ ref[2]가 각각 arr[0] ~ arr[2]의 레퍼런스가 되는 것이다.
배열의 레퍼런스는 반드시 배열의 크기를 명시해 주어야 한다.
레퍼런스를 리턴하는 함수
int function() {
int a = 2;
return a;
}
int main() {
int b = function(); // function 안에 정의된 a 변수의 값이 b에 복사
return 0;
}
복사되었으므로 function이 종료되고 나면 a는 메모리에서 사라지게 된다.
int& function() {
int a = 2;
return a;
}
int main() {
int b = function();
b = 3;
return 0;
}
function()의 리턴타입은 int&이므로 참조라를 리턴하게 된다. 그런데 function()안에 정의되어 있는
a는 함수의 리턴과 함께 사라지므로 오류가 발생하게 된다.
int& ref = a;
int b = ref; // a가 사라짐
이와 같이 레퍼런스는 존재하는데 원래 참조하던것이 사라진 레퍼런스를
댕글링 레퍼런스 (Dangling reference)라고 한다.
따라서 레퍼런스를 리턴하는 함수에서 지역 변수의 레퍼런스를 리턴하지 않도록 조심해야 한다.
외부변수의 레퍼런스를 리턴하는 경우
int& function(int& a){
a = 5;
return a; // 인자로 받은 레퍼런스를 그대로 리턴한다
}
int main() {
int b = 2;
int c = function(b);
return 0;
}
function(b)를 실행한 시점에서 a는 main의 b를 참조하고 있게 된다. 따라서 function()이 리턴한
참조자는 아직 살아있는 변수 b를 참조한다.
결국 c = 5;와 같은 결과가 되는것이다.
레퍼런스를 리턴하게 되면 레퍼런스가 참조하는 타입의 크기와 상관없이 한 번의 주소값 복사로
전달이 끝나게 되므로 아주 효율적이다.
참조자가 아닌 값을 리턴하는 함수를 참조자로 받기
int function() {
int a = 5;
return a;
}
int main() {
int& c = function();
return 0;
}
상수가 아닌 레퍼런스는 function() 함수의 리턴값을 참조할 수 없다
함수의 리턴값은 해당 문장이 끝난 후 바로 사라지는 값이므로 참조자를 만들게 되면
바로 댕글링 레퍼런스가 되버리기 때문이다.
int function() {
int a = 5;
return a;
}
int main() {
const int& c = function();
return 0;
}
예외적으로 상수 레퍼런스로 리턴값을 받게 되면 해당 리턴값의 생명이 연장되므로 함수의 리턴값을
참조하는 것이 가능하다.
생명이 연장되는 기간은 레퍼런스가 사라질때까지이다.