변수란, 할당된 하나의 메모리 공간에 붙여진 이름이다. 우리는 그 이름을 통해서 해당 메모리 공간에 접근할 수 있다.
int num = 10;
우리는 10으로 초기화된 메모리 공간에 num 이라는 이름을 붙이고, 해당 이름으로 그 공간에 접근할 수 있는 것이다.
& 연산자는 이미 선언된 변수의 앞에 오는 변수의 주소 값을 반환하는 연산자이다.
하지만, 새로 선언되는 변수 앞에 쓰이면 참조자의 선언이 된다.
int num = 10;
int *ptr = # // 변수 num의 주소값을 포인터 ptr에 저장해라
int &num2 = num; // 변수에 대한 참조자 num2에 대한 선언
이렇게 되면, 10으로 초기화된 메모리 공간에 num이라는 이름과 num2라는 이름이 하나 더 생긴다.
C++에서 참조자와 변수는 구분되지만, 참조자의 역할은 변수와 같다. 둘은 현재 동일한 메모리 공간을 참조하고 있다!
참조자의 수에는 제한이 없다.
참조자는 별칭이다. 별명의 개수에는 제한이 없듯이, 참조자 역시 하나의 메모리 공간에 참조자를 통해 여러개의 이름을 붙일 수 있다.
참조자는 변수에 대해서만 선언이 가능하다.
참조자는 메모리 공간 자체에 이름을 붙이는 것이 아니라 이미 정해진 이름에 별칭을 붙이는 것이다. 이 점에서 변수와 다르다.
참조자는 선언과 동시에 누군가를 참조해야한다.
선언을 먼저한 후, 나중에 어떤 변수를 참조하는 것은 불가능하다. 참조의 대상을 바꾸는 것 역시 불가능하다. 포인터 변수의 선언과 마찬가지로 NULL로의 초기화 역시 불가능하다.
Call by Vlaue VS Call by Reference
Call-by-value 로 정의된 함수의 내부에서는, 함수 외부에 선언된 변수의 접근이 불가능하다.
void Call_by_Value(int num1, int num2){
int re = num1;
num1 = num2;
num2 = re;
}
int main(){
int val1 = 10;
int val2 = 20;
Call_by_Value(val1, val2);
}
// val1 = 10;
// val2 = 20;
Call_by_Value 함수에 의해서 변수에 저장된 값들이 변하지 않음을 확인할 수 있다. 외부에서 변수에 접근하기 위해 Call by Reference 기반의 함수가 필요하다.
void Call_by_reference(int * num1, int * num2){
int re = *num1;
*num1 = *num2;
*num2 = re;
}
int main(){
int val1 = 10;
int val2 = 20;
Call_by_reference(&val1, &val2);
}
// val1 = 20;
// val2 = 10;
포인터를 매개변수로 넘기게 되면서 외부에서 선언된 변수에도 접근 가능한 것을 확인할 수 있다.
다만, 아래의 경우를 보자.
int * func(int * ptr){
return ptr+1;
}
포인터를 매개변수로 받고 있지만, 위의 함수는 주소값을 증가시켜서 그 값을 리턴하고 있다. 위는 'value'를 매개로 한다. 다만, 그 '값'이 주소값인 것 뿐이다.
void Call_by_reference(int & num1, int & num2){
int re = num1;
num1 = num2;
num2 = re;
}
int main(){
int val1 = 10;
int val2 = 20;
Call_by_reference(val1, val2);
}
// val1 = 20;
// val2 = 10;
위의 참조자의 성질에 따르면 참조자는 선언과 동시에 변수로 초기화되어야 한다. 의아할 수 있으나 사실 매개변수는 함수가 호출되어야 초기화가 진행되는 변수들이므로 걱정할 필요 없다.
코드를 분석하는 과정에 있어, 함수의 호출 문장만을 보고도 어느 정도 특성을 판단할 수 있어야 한다. 하지만, 참조자를 사용한 함수의 경우, 변수의 값이 변할지 변하지 않을지를 하나하나 뜯어봐야 알 수 있다.
C언어에서는 함수의 호출 문장만을 보고도 값의 변경 여부를 알 수 있지만, C++에서는 이것이 불가능하다는 단점이 있다.
이를 해결하기 위해 const 키워드를 이용한다.
함수 내에서, 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서 함수 원형을 보고 값이 변하지 않을 것임을 암시하도록 하자.
int& Func(int n){
int num = 10;
num += n;
// Func 함수의 지역변수 num을 참조자로 반환하고 있다.
return num;
}
int main(){
int &ref = Func(10);
}
위와 같이 구현할 경우, 함수가 반환되면서 지역변수 num에 ref라는 참조자가 하나 더 선언된다.
근데 반환되자마자 지역변수인 num은 사라진다...
const int num = 20;
const int &ref = num;
int num = 20 + 30;
// 같은 프로그램 내에서 표현되는 "리터럴 상수"
// -> 다음 행으로 넘어가면 존재하지 않는 임시적으로 존재하는 값이다.
연산을 위해서는 20, 30 모두 메모리 공간에 저장은 되어야한다. 하지만, 이후 재참조가 가능하진 않다.
const int &ref = 1;
// 임시변수를 1이라는 숫자로 상수화
// ref라는 참조자가 그 임시변수를 참조
// 결과적으로 상수화된 변수를 참조할 수 있다.
이렇게 상수를 참조하려면, 상수가 계속 메모리 공간에 남아있어야만 가능할 것이다. C++에서는 const 참조자를 이용해서 상수를 참조할 때, '임시변수'라는 것을 만들고 여기에 상수를 저장 후, 참조자가 임시변수를 참조하게 한다.
이를 허용하게 되면서,
int Func(const int &a, const int &b){
return a + b;
}
int mian(){
cout << Func(1,2);
return 0;
}
Func 함수에 인자 전달을 목적으로 변수를 생성하는 번거로운 일을 피할 수 있게 되었다.
<출처 : 윤성우의 열혈 C++ 프로그래밍>
위 책을 공부하며 정리한 내용입니다.