지금까지는 두 가지 변수 타입(일반 변수, 포인터)을 공부했다.
지금부터 보게 될 참조형은 다른 객체 또는 값의 별칭으로 사용되는 C++ 타입으로,
C++은 non-const 값 참조형, const 값 참조형, r-value 참조형을 지원한다.
#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;
}
C언어와는 달리 C++에서는 다른 변수나 상수를 가리키는 방법으로 참조자(레퍼런스 - reference)를 사용한다.
위 예제에서 선언하면서 another_a는 a의 참조자, 즉 a의 또다른 이름으로 컴파일러에게 알려주는 꼴이 된 것이다.
따라서 another_a는 a의 또다른 이름이라고 컴파일러에게 알려주는 것이다.
int& another_a;
// 실행 안되는 코드
반면에 포인터의 경우
int* p;
// 실행되는 코드
int a = 10;
int &another_a = a; // another_a 는 이제 a 의 참조자!
int b = 3;
another_a = b; // **a = another_a 의 공간에 b의 값을 대입함**
#include <iostream>
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;
}
// 참조자 이해하기
#include <iostream>
int main() {
int x;
int& y = x;
int& z = y;
x = 1;
std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;
y = 2;
std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;
z = 3;
std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;
}
// y, z는 x에 대한 레퍼런스
모호한 질문이다.
레퍼런스 자체는 메모리상에 존재하지 않는다.
그러나 레퍼런스가 별칭하는 값은 메모리상에 존재한다.
아래 코드에서 a는 func1의 실행이 끝난 이후 별칭으로서도 사라진다.
#include <iostream>
int func1(int &a){
a++;
return a;
}
int &func2(int &a){
a++;
return a;
}
int main(){
int x = 10;
int y = 20;
int& j = x;
std::cout << fn1(x) << std::endl;
std::cout << fn2(y) << std::endl;
}
리터럴에 대한 참조자는 불가
단, 상수 참조자로 선언한다면 리터럴도 참조 가능
null 참조 역시 상수 참조자(const 사용)로 사용하는 경우 가능
#include <iostream>
int main() {
int &ref = 4; // 불가
const int &ref2 = 4; // 가능
std::cout << ref << std::endl;
}
#include <iostream>
int main() {
int a,b;
int &arr[2] = {a,b}; // 불가한 코드: 레퍼런스로 배열을 정의하는 것은 불가
int arr[3] = {1, 2, 3};
int(&ref)[3] = arr; // 가능한 코드: 배열의 레퍼런스로 ref를 정의하는 것은 가능
// 따라서 ref[0]부터 ref[2]가 각각 arr[0]부터 arr[2]의 레퍼런스가 됨
ref[0] = 2;
ref[1] = 3;
ref[2] = 1;
std::cout << arr[0] << arr[1] << arr[2] << std::endl;
return 0;
}
int function() {
int a = 2;
return a;
}
int main() {
int b = function();
return 0;
}
위와 같이 코드를 실행하면 function 안에 정의된 a라는 변수의 값이 b에 복사됨
function이 종료되면 a는 메모리에서 사라짐(main 안에서는 a를 만날 길이 없음)
int function() {
int a = 2;
return a;
}
int main() {
int b = function();
return 0;
}
불가하다.
dangling reference가 되어버리기 때문이다.
즉, 리턴하는 function 안에 정의되어 있는 a는 함수의 리턴과 함께 사라진다.
❗ 레퍼런스를 리턴하는 함수에서 지역 변수의 레퍼런스를 리턴하지 않도록 조심해야 함.
int& function(int& a) {
a = 5;
return a;
}
int main() {
int b = 2;
int c = function(b);
return 0;
}
function()은 인자로 받은 레퍼런스인 a를 그대로 리턴함
function(b)를 실행한 시점에서 a는 main의 b를 참조하고 있음(a는 b의 레퍼런스)
: 레퍼런스가 참조하는 타임의 크기와 상관 없이 딱 한번의 주소값 복사로 전달이 끝나게 됨.
매우 효율적임!
int function() {
int a = 5;
return a;
}
int main() {
int& c = function();
// 컴파일 오류 발생
return 0;
}
컴파일 오류 발생
-> 상수가 아닌 레퍼런스가 function 함수의 리턴값을 참조할 수 없음
이유
-> 함수의 리턴값은 해당 문장이 끝난 후 바로 사라지는 리터럴과 다름이 없음.
따라서 참조자를 만들게 되면 바로 다음에 댕글링 레퍼런스가 되어버림.
그런데 다음과 같이 const 참조자로 받으면 문제 없이 컴파일되는 것을 확인할 수 있음.
#include <iostream>
int function() {
int a = 5;
return a;
}
int main() {
const int& c = function();
std::cout << "c : " << c << std::endl;
return 0;
}
상수 레퍼런스로 리턴값을 받게 되면 해당 리턴값의 생명이 연장됨.
BUT 연장 기간은 레퍼런스가 사라질 때까지임.
~82p