Reference(참조자)
레퍼런스의 도입
- C++에서는 다른 변수나 상수를 가리키는 방법으로 또 다른 방식을 제공하는데, 이를 바로 레퍼런스(reference)라고 부른다.
- 레퍼런스를 정하는 방법은 가리키고자 하는 타입 뒤에
&
를 붙이면 된다. int
형 변수의 레퍼런스를 만들고 싶을 때에는int&
를,double
의 레퍼런스를 만드려면double&
로 하면 된다.int*
와 같은 포인터 타입의 레퍼런스를 만드려면int*&
로 쓰면 된다.- 레퍼런스는 원래의 변수의 또 다른 이름을 설정하는 것과 같다.
레퍼런스**는 한 번 별명이 되면 절대로 다른 이의 별명이 될 수 없다.
즉, 어떤 변수의 레퍼런스가 되버리면, 더 이상 다른 변수를 참조할 수 없게 된다.
- 레퍼런스는 메모리 상에 존재하지 않을 수 도 있다.
int a = 10; int* p = &a;
-
위 포인터 변수 p는 메모리 상에서 8 바이트를 차지한다.(32비트의 경우 4바이트)
int a = 10; int& another_a = a;
-
레퍼런스의 경우 메모리 상에 존재하지 않게 된다. 물론 그렇다고 해서 항상 존재하지 않는 것은 아니고 아래 예와 같은 예외가 존재한다.
#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; }
-
위에서 중요한 부분은
int change_val(int &p)
함수 선언부다. 레퍼런스의 경우 반드시 처음에 누구의 별명이 될 것인지 정해줘야 한다는 규칙이 있어 이 같은 선언은 문제가 있을 것으로 보이지만 사실 p가 정의되는 순간은change_val(number)
로 호출할 때 이므로 사실상int& p = number
가 실행된다고 보면 된다. 그렇기 때문에 전혀 문제가 없는 선언이다.
-
- 상수에 대한 레퍼런스
#include <iostream> int main() { int& ref = 4; // 에러가 발생하는 구문 std::cout << ref << std::endl; }
-
위 코드는 에러가 발생하는데 그 이유는 상수값 자체는 리터럴이기 때문이다.
-
리터럴은 그 차체로 변경할 수 없는 값인데 위 같은 코드가 실행된담녀
ref = 5;
와 같이 리터럴의 값을 바꾸는 행위가 가능하게 된다. -
따라서 C++ 문법상 상수 리터럴을 일반적인 레퍼런스가 참조하는 것은 불가능하게 돼있다.
const int& ref = 4;
-
단 예외적으로 상수 레퍼런스로 선언하면 리터럴도 참조 할 수 있다.
-
- 레퍼런스의 배열과 배열의 레퍼런스
int a, b; int& arr[2] = {a, b};
-
위 코드와 같은 레퍼런스 배열의 정의는 에러를 발생 시킨다.
-
C++의 문법 상 배열의 이름은 (arr)의 첫 번째 원소의 주소값으로 변환이 될 수 있어야 한다. 이 때문에
arr[1]
과 같은 문장이*(arr + 1)
로 바뀌어서 처리될 수 있다. -
그런데 주소값이 존재한다는 의미는 해당 원소가 메모리상에 존재한다라는 의미와 같다.
-
하지만 레퍼런스는 특별한 경우가 아닌 이상 메모리 상에서 공간을 차지 하지 않는다. 따라서 이러한 모순 때문에 레퍼런스들의 배열을 정의하는 것은 언어 차원에서 금지되어 있다.
-
그렇다고 해서 그와 반대인 배열들의 레퍼런스가 불가능한 것은 아니다.
#include <iostream> 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; }
-
ref
가arr
배열을 참조하도록 했다. -
따라서
ref[0]
부터ref[2]
가 각각arr[0]
부터arr[2]
의 레퍼런스가 된다. -
포인터와는 다르게 배열의 레퍼런스의 경우 참조하기 위해선 반드시 배열의 크기를 명시해야 한다.
-
따라서
int (&ref)[3]
이라면 반드시 크기가 3인int
배열의 별명이 되어야 하고int (&ref)[5]
라면 크기가 5인int
배열의 별명이 되어야 한다.
-
- 레퍼런스를 리턴하는 함수
int function() { int a = 2; return a; } int main() { int b = function(); return 0; }
-
문제 없이 컴파일이 완료되면 b에 a의 값이 복사되어 저장된다. 즉, 값만 복사되고 a는 메모리에서 해제된다.
int& fucntion() { int a = 2; return a; } int main() { int b = function(); return 0; }
-
이 코드는 런타임 에러를 발생시킨다.
-
function
의 타입은int&
이며 참조자를 리턴하게 된다. -
그런데 문제는
function
안에 정의되어 있는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에 현재의 b의 값인 5를 대입하는 것과 동일한 문장이 된다.
-
레퍼런스를 리턴하는 경우의 장점
- C언어에서 엄청나게 큰 구조체가 있을 때 해당 구조체 변수를 그냥 리턴하면 전체 복사가 발생해야 해서 시간이 오래걸리지만, 해당 구조체를 가리키는 포인터를 리턴하면 그냥 포인터 주소 한 번 복사로 매우 빠르게 끝난다.
- 마찬가지로 레퍼런스를 리턴하게 되면 레퍼런스가 참조하는 타입의 크기와 상관 없이 딱 한 번의 주소값 복사로 전달이 끝나게 된다.
-
Author And Source
이 문제에 관하여(Reference(참조자)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dodidos96/Reference참조자저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)