c++ reference 개념
아주 기본 개념이지만 완벽하게 이해하지 않고 개발하려면 매우 혼란스럽다. reference 개념을 확실하게 이해해야 한다.
c에서는 pointer의 개념, 주소의 개념이 정말 중요했다면, 추가로 c++에서는 reference 개념을 정확히 이해해야 한다.
변수
- 메모리의 특정 위치를 가리키는 이름이다.
- 코드 안에서 해당 메모리에 접근하기 위해서 사용한다.
레퍼런스
- 기존 변수(메모리)에 또 다른 이름(alias)를 부여하는 것
- 아래 코드에서 n이 기존 변수 이며, r이 새로운 reference 변수 이름이다.
#include <iostream>
int main()
{
int n = 10;
n = 20;
//int* p = &n; // 포인터 변수에서 주소를 꺼내는 방법
int& r = n; // 레퍼런스 : 기존 변수의 또다른 alias (별명) 을 만든다.
r = 30; // n = 30
std::cout << n << std::endl; // 30
std::cout << r << std::endl; // 30
std::cout << &n << std::endl;
std::cout << &r << std::endl;
}
n의 주소값 (&n)과 r의 주소값(&r) 이 동일함을 확인 할 수 있다.
reference 개념 이해를 위한 예제
해당 예제에서 각 변수 a,b,c가 호출되었을 때 값들이 어떻게 저장되고 어떤 값이 증가(++) 수행되는지 과정을 살펴볼 수 있다.
#include <iostream>
void f1(int n) { ++n;}
void f2(int* p) { ++(*p);}
void f3(int& r) { ++r;} // int& r = c
int main()
{
int a = 0, b = 0, c = 0;
f1(a);
f2(&b); // b가 변경될수 있다고 예측가능.
f3(c); //
std::cout << a << std::endl; // 0
std::cout << b << std::endl; // 1
std::cout << c << std::endl; //
int* p = &n;
int& r = n;
}
1) a, b, c 세 개의 변수 공간이 생성됨
int a = 0, b = 0, c = 0;
2) f1(a); 수행이 되면,
또 다른 n 이라는 메모리 공간(5000)이 만들어진다.
n 이라는 값을 새로운 메모리 공간(5000)의 n 이 {++n} 에 의해 증가 된다.
call by value 형태로 원본인 a 값은 수정되지 않는다.
void f1(int n) { ++n;} // 인자를 받을 때 값으로 받는다!
int main()
{
int a = 0, b = 0, c = 0;
f1(a);
}
3) f2(&b); 가 수행되면,
포인터 변수가 만들어지면서 2000이라는 주소가 p의 메모리 공간(6000)에 저장된다.
*p 내가 가리키는 공간(2000)의 값을 ++ 시킨다.
call by pointer 형태로 원본 값(b)이 수정된다.
void f1(int n) { ++n;}
void f2(int* p) { ++(*p);}
int main()
{
int a = 0, b = 0, c = 0;
f1(a);
f2(&b); // b가 변경될수 있다고 예측가능.
}
4) f3(c); 를 수행하면
f3 안의 r 주소를 출력해 보면 c의 주소와 동일하다.
r이 가리키는 c 의 값을 증가시킨다.
reference는 포인터와 마찬가지로 원본을 수정할 때 사용한다.
#include <iostream>
void f1(int n) { ++n;}
void f2(int* p) { ++(*p);}
void f3(int& r) { ++r;} // int& r = c
int main()
{
int a = 0, b = 0, c = 0;
f1(a);
f2(&b); // b가 변경될수 있다고 예측가능. (오히려 reference보다 가독성이 좋은 면이 있다.)
f3(c); // c가 변경될 수 있다고 예측하기가 애매하다.
// (reference 사용이 간편하다는 장점,
// 하지만 직관적이지 않은 단점이 있다 call by value와 형태가 동일!.)
std::cout << a << std::endl; // 0
std::cout << b << std::endl; // 1
std::cout << c << std::endl; // 1
int* p = &n; // 포인터 변수는 초기화 할때 오른쪽에 주소를 쓴다.
int& r = n; // 레퍼런스 변수는 초기화 할 때 변수를 그대로 쓴다.
}
Pointer VS Reference
포인터와 레퍼런스 변수 사용 방식이 다르고 그 차이를 정확하게 이해해야 한다.
이 코드 세 줄이 수행되면 아래와 같이 메모리 공간이 생성될 것이다.
int n = 10;
// ============ 포인터 ===================
int* p1 = &n; // 1. 변수 주소로 초기화,
// 2. *연산자 사용 *p1 = 10
// 3. NULL 가능,
// 4. 포인터 변수의 주소를 출력
// ============ 레퍼런스 ===================
int& r1 = n; // 1. 변수 이름으로 초기화 (초기값 반드시 필요),
// 2. *연산자 필요없음
// 3. NULL 불가
// 4. 기존 변수와 동일 주소를 가짐
포인터 p2는 가리키는 곳은 없는 상태인 null 이 될 수 있다.
하지만 레퍼런스 r2는 null이 불가하며, 연결짓는 변수가 지정되어야 한다. 레퍼런스 변수는 별도의 공간이 생성되지 않고 연결만 되기 때문이다.
#include <iostream>
int main()
{
int n = 10;
int* p1 = &n;
int& r1 = n;
int* p2 = 0; // null pointer 가능.
int& r2; // error. : 초기값 없어서 에러 발생
*p1 = 20; // * 연산자 사용
r1 = 20; // * 연산자 필요없음 ==> 자동 * 연산되는 포인터
if ( p1 != 0 ) {} // pointer는 NULL이 가능하기 때문에 nullptr 체크 중요함!!!
if ( r1 != 0 ) {} // r1은 if 문으로 조사할 필요가 없다.
std::cout << &p1 << std::endl; // 포인터 변수의 주소값이 출력됨.
std::cout << r1 << std::endl; // 기존 변수와 동일
}
int& r2; 초기값 없어서 에러 발생함.
main.cpp:11:10: error: ‘r2’ declared as reference but not initialized
에러 발생 부분을 주석처리 후 실행하면 아래 결과를 볼 수 있다.
'C++' 카테고리의 다른 글
[C++] Explicit Casting (명시적 캐스팅) (0) | 2021.08.14 |
---|---|
[C++] const reference, return by reference (0) | 2021.08.14 |
[C++] range-for, if init, if constexpr (0) | 2021.08.12 |
[C++] 함수 : Lamda expression (람다 표현식) (0) | 2021.08.11 |
[C++] 함수 : constexpr function (0) | 2021.08.10 |
댓글