용어 정리
참조자 = 참조 = 레퍼런스
C++에서 참조는 변수나 상수를 가리키는 방법입니다.
즉, 또 다른 이름으로 똑같은 변수나 상수라고 컴파일러에게 알려주는 것입니다.
아래 예제를 보면
int a = 3; // a를 3으로 선언하고
int& another_a = a; // another_a에 a를 참조하면
another_a = 5; // another_a는 a의 별명 역할을 하게 됩니다.
std::cout << a; // a를 출력합니다.
5
another_a는 a를 참조했고
another_a에 5를 할당했습니다.
이상태에서 a를 출력해봤더니 5가 나옵니다.
즉, another_a를 변경하면 사실상 a를 변경하는 것과 같습니다.
C++에서 참조의 특징
C++에서는 참조 ( & ) 가 포인터( * ) 와는 달리
한 번 초기화되면 다른 변수를 가리킬 수 없습니다.
왜냐면 처음 참조한 변수를 끝까지 가리키기 때문에 선언 이후에
다른 변수로 변경은 절대 불가능합니다.
예제를 보면
int a = 10;
int &another_a = a; // another_a 는 이제 a 의 참조자!
std::cout << a << std::endl; // a 출력
int b = 3;
another_a = b; // b로 참조하는 것으로 변경이 불가능!
//따라서 another_a는 이제부터 a랑 똑같으니 a = b로 여겨짐
std::cout << a; // a 출력
10
3
위 상황에서
&another_a = b;
another_a에 b를 할당 하면
&a = b;
a에 b를 할당한다는 것과 똑같으니 에러가 뜹니다
참조 vs 포인터
참조는 한번 초기화 이후 가리킬 대상이 바뀔 수 없지만
포인터는 자유롭게 바뀔 수 있습니다.
포인터 예시)
int a = 10;
int *p = &a; // 포인터 p 는 a 를 가리킨다.
//이 때 포인터 p는 메모리에서 8바이트를 차지한다.
printf("%d \n", *p);
int b = 3;
p = &b; // 이제 포인터 p 는 a 를 버리고 b 를 가리킨다
//얘도 8 바이트
printf("%d \n", *p);
10
3
번외)
32비트 프로세서의 범용 레지스터의 크기는 32비트 즉 4바이트이고,
64비트 프로세서의 범용 레지스터의 크기는 64비트, 즉 8바이트이다.
그래서 위에선 int * p가 컴퓨터 프로세서의 비트따라 달라짐
또한, 포인터와는 달리 참조는 메모리 상에 공간 차지 안하도록 할 수 있습니다.
참조하는 대상으로 바꿔치기만 하면 되기 때문에 메모리 자리를 차지할 필요가 없기 때문입니다.
함수 인자로 레퍼런스 받는 예시
#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;
}
5
3
change_val 함수로 인자(number)를 &p으로 담아서 참조하도록했습니다.
그리곤 p를 3으로 변경했더니 참조한 number이 3으로 변경되어 출력이 됩니다.
상수에 대한 참조자
#include <iostream>
int main() {
int &ref = 4;
std::cout << ref << std::endl;
}
error C2440: 'initializing' : cannot convert from 'int' to 'int &'
4는 상수입니다. 즉, [[리터럴(literal)]] 값이므로 참조가 불가능합니다.
대신 const 키워드를 이용하면 상수 참조자로 참조가 됩니다.
const int &ref = 4;
이제부턴 ref가 4로 고정이 되고 값이 변경되지 않습니다.
참조의 배열, 배열의 참조
C++에서는 배열 참조가 특이합니다.
C++은 C언어의 문법이 허용되는게 일반적이기 때문에
int arr[2] = {1,2}
이런 코드에서 배열 arr은 주소값이 생기고, 주소값이 생긴다는 말은 메모리에 존재한다는 것입니다.
원래 문법대로면 arr[2]는 *(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 참조 배열은 괄호()로 먼저 감싸준 뒤
- 참조 배열의 크기를 지정해야하므로 참조할 배열 크기인 [3]을 붙여줍니다.
- 배열의 시작 주소인 arr을 그대로 참조 시킵니다.
이차원 배열도 똑같이 하면 됩니다.
int arr[3][2] = {1, 2, 3, 4, 5, 6};
int(&ref)[3][2] = arr;
레퍼런스 리턴하는 함수
int function() {
int a = 1;
return a;
}
int main() {
int b = function();
return 0;
}
main()에서 b에 function() 호출로 a 값(2)을 복사해준 뒤 a는 function()의 지역변수이므로 사라집니다.
a의 사라진다는 지역변수 특성이 중요합니다.
만약 아래처럼
int& function() {
int a = 1;
return a;
}
function()이 int& 타입을 리턴한다면 main()함수 안에 입장에선 int& function = a;와 똑같이 되고
이 function 값을 b같은 변수에 넣는다면 에러가 뜨게 됩니다.
왜냐하면 function는 a를 참조하는데 a가 지역변수라 사라지기 때문입니다.
이렇게 참조하던 대상이 사라진 레퍼런스를 Dangling reference라고 합니다.
함수를 참조자로 받기
int function() {
int a = 1;
return a;
}
int main() {
int& c = function();
return 0;
}
보다시피 int& c 를 main()에 선언하여 function()의 리턴 값을 참조하고 있습니다.
이 경우, 아까랑 똑같이 function()의 리턴값인 a가 지역변수로 사라지기에 에러가 뜨게 됩니다.
그러면 어떻게 해야 에러가 안뜰까요?
정답은 const 키워드를 사용하시는겁니다.
const int& c = function();
이렇게 선언을 하면 상수 레퍼런스가 되어 리턴값 a의 생명이 레퍼런스의 생명까지 연장되게됩니다.
'그냥 개발글 > C++' 카테고리의 다른 글
리터럴(literal) (0) | 2023.12.12 |
---|---|
오버로딩 (Overloading) (0) | 2023.12.12 |
동적 할당 new, delete (0) | 2023.12.12 |
클래스 (0) | 2023.12.12 |