C++ Reference(참조자)
모두의 코드 내용을 공부하고 정리한 내용입니다.
Reference(참조자)
- 타입뒤에 &를 붙여서 가리키고자 하는 주소를 받아낸다.
- C언어와 다르게 선언할 때 초기화를 해야하며 한 번 어떤 변수의 참조자가 되면 더 이상 다른 참조를 할 수 없다
int a = 10; int &another_a = a; // another_a 는 이제 a 의 참조자! int b = 3; another_a = b; // 잘못된 방법
- 레퍼런스는 특별한 경우가 아니면 메모리를 차지하지 않는다
- 위 코드에서 another_a가 쓰이는 자리는 모두 a로 바꿔치기 하면되어 another_a 공간 할당이 필요없어진다.
참조자의 참조자
- 어떤 참조자에 대한 참조자는 만들 수 있을까?
- 불가능하다. 궅이 별명의 별명을 만들 필요가 없다.
- 실제로 C++ 문법 상 참조자의 참조자를 만드는 것은 금지되어 있다.
상수에 대한 참조자
int &ref = 4;
- 컴파일 오류가 날 것이다.
- 상수는 리터럴이기 때문에 레퍼런스로 참조를 한다면 리터럴 값을 바꾸는 말도 안되는 행위가 가능하기 때문이다.
- 하지만 다음과 같은 문법은 가능하다.
const int &ref = 4;
- 하지만 다음과 같은 문법은 가능하다.
- 상수는 리터럴이기 때문에 레퍼런스로 참조를 한다면 리터럴 값을 바꾸는 말도 안되는 행위가 가능하기 때문이다.
배열의 레퍼런스
배열의 레퍼런스는 불가능
int & arr[2] = {a, b}; // 의미: 2개의 int형 레퍼런스의 배열
- C++에서 배열은 배열의 이름은 첫번째 주소값으로 변환될 수 있어야한다.
- 그렇기에
*(arr+1)
이 가능하게 하지.
- 그렇기에
- 주소값을 가지면 해당 원소가 메모리에 존재한다는 의미.
- 하지만 레퍼런스는 특별한 경우가 아니면 메모리 상에서 공간을 차지하지 않는다.
- 위에서 말했듯이 컴파일러 입장에서는 a와 b는 둘다 같은 것으로 인식하여 a는 메모리상에 존재하지 않는다.(a 자리에 b를 사용하기 떄문에)
- 따라서 메모리 상에 없으니 배열이 될 수 없다
- 하지만 레퍼런스는 특별한 경우가 아니면 메모리 상에서 공간을 차지하지 않는다.
하지만 항상 메모리 상에 존재하지 않는 것은 아니다.
- 예를 들어 함수 인자를 레퍼런스로 받으면 메모리가 할당된다.
- 함수에 전달된 주소값은 stack 메모리 공간에 저장되어 사용된다. ==> 함수의 공간에서만 사용
- 이렇게 함수에 레퍼런스를 넘겼을 경우 main에서 할당된 주소를 사용하는게 아니라 함수에서 할당된 메모리 주소를 사용한다.
레퍼런스의 배열
- 레퍼런스의 배열은 가능하다
int arr[3] = {1, 2, 3};
int (&ref)[3] = arr; // 의미: 3개의 int형으로 이루어진 배열의 레퍼런스
// ==> ref[0] 부터 ref[2] 가 각각 arr[0] 부터 arr[2] 의 레퍼런스
- 포인터와는 다르게 배열의 레퍼런스의 경우 참조하기 위해선 반드시 배열의 크기를 명시해야 한다.
Dangling Reference(댕글링 레퍼런스)
int& function() {
int a = 2;
return a;
}
- 레퍼런스는 있는데 원래 참조 하던 것이 사라진 레퍼런스
- 어떤 지역변수를 참조자를 만든다면(어떤 함수에서) 그 지역을 벗어나는 순간 참조자가 가르키는 주소가 없어진다.
외부 변수의 레퍼런스를 리턴
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 언어에서 엄청나게 큰 구조체가 있을 때 해당 구조체 변수를 그냥 리턴하면 전체 복사가 발생해야 해서 시간이 오래걸리지만, 해당 구조체를 가리키는 포인터를 리턴한다면 그냥 포인터 주소 한 번 복사로 매우 빠르게 끝난다.
-
마찬가지로 레퍼런스를 리턴하게 된다면 레퍼런스가 참조하는 타입의 크기와 상관 없이 딱 한 번의 주소값 복사로 전달이 끝나게 됩니다. 따라서 매우 효율적!
참조자가 아닌 값을 리턴하는 함수를 참조자로 받기
int function() {
int a = 5;
return a;
}
int main() {
int& c = function();
return 0;
} // 컴파일 오류!!!
- 아까전 상황과 마찬가지로 함수의 리턴값은 해당 문장이 끝난 후 바로 사라지는 값이기 때문에 참조자를 만들게 되면 바로 다음에 댕글링 레퍼런스가 되버리기 때문이다.
- 해당 코드에서 다음과 같은 코드를 작성하면 런타임 오류가 발생한다.
int& c = function();
c = 2;
- 하지만 예외적인 상황이 있다.
int function() {
int a = 5;
return a;
}
int main() {
const int& c = function();
std::cout << "c : " << c << std::endl;
return 0;
}
- 원칙상 함수의 리턴값은 해당 문장이 끝나면 소멸되는 것이 정상이다.
- 따라서 기존에 int& 로 받았을 때에는 컴파일 자체가 안됨
- 하지만 예외적으로 상수 레퍼런스로 리턴값을 받게 되면 해당 리턴값의 생명이 연장된다.
- 연장되는 기간은 레퍼런스가 사라질 때 까지…
함수에서 값 리턴(int F()) | 함수에서 참조자 리턴(int& F()) | |
---|---|---|
값 타입으로 받음 (int a = f()) |
값 복사됨 | 값 복사됨. 다만 지역 변수의 레퍼런스를 리턴하지 않도록 주의 |
참조자 타입으로 받음 (int& a = f()) |
컴파일 오류 | 가능. 다만 마찬가지로 지역 변수의 레퍼런스를 리턴하지 않도록 주의 |
상수 참조자 타입으로 받음 (const int& a = f()) | 가능 | 가능. 다만 마찬가지로 지역 변수의 레퍼런스를 리턴하지 않도록 주의 |
개인 공부 기록용 블로그입니다.
잘못된 곳은 댓글 혹은 메일을 지적해주시면 감사하겠습니다!
댓글남기기