참조자를 이용한 Call-by-reference와 const 참조자


아래의 코드를 보고 예상되는 출력결과를 이야기 해보자.

int num = 24 ;

func(num) ;

cout << num << endl ;


C언어 관점에서는 100% 24가 출력된다. 


그러나 C++에서는 얼마가 출력될 지 알 수 없다.


void func(int num) { . . . } 다음과 같이 정의되어 있을 경우 24가 출력되지만


void func(int &ref) { . . . } 다음과 같이 정의되어 있다면 참조자를 이용해서 num에 저장된 값을 변경할 수 있다.


여기서 참조자의 단점이 드러난다. 코드를 분석하는 과정에 있다면, 함수의 호출문장만 보고도 함수의 특성을 어느정도 판단할 수 있어야 한다. 그러나 참조자를 사용하는 경우, 함수의 원형을 확인해야 하고, 확인결과 참조자가 매개변수의 선언에 와있다면, 함수의 몸체까지 문장 단위로 확인을 해서 참조자를 통한 값의 변경이 있는지 확인해야 한다.


이러한 단점을 어느정도 극복하기 위해 const 키워드를 사용한다.


void func(const int &ref) { . . . }


다음 코드의 함수 원형을 보면 참조자 ref에 const 선언이 추가되었다. 이는 다음의 의미를 갖는다.


"함수 func 내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다"


따라서 참조자를 사용할 경우 다음의 원칙을 정하고 가급적 이 원칙을 지켜주는 것이 좋다.


함수 내에서 참조자를 통한 값의 변경을 진행하지 않을 경우

참조자를 const로 선언해서, 함수의 원형만 봐도 값의 변경이 이루어지지 않음을 알 수 있게 한다.


반환형이 참조형(Referencfe Type)인 경우


함수의 반환형에도 참조형이 선언될 수 있다. 다음의 예제를 통해 살펴보자


  1. int& reffunc(int &ref){  
  2.    ref++;  
  3.    return ref;  
  4. }  


위의 함수에서는 매개변수로 참조자가 선언되었는데, 이 참조자를 그대로 반환하고 있다.


하지만 다음과 같이 참조자를 반환해도 반환형은 참조형이 아닐 수 있다. 이점에 유의하자.


  1. int reffunc(int &ref){  
  2.    ref++;  
  3.    return ref;  
  4. }  


이 둘의 차이점을 생각해 보면서 다음의 예제를 통해 살펴보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int &reffunc(int &ref) {  
  5.     ref++;  
  6.     return ref;  
  7. }  
  8.   
  9. int main() {  
  10.     int num1 = 1;  
  11.     int &num2 = reffunc(num1);  
  12.   
  13.     num1++;  
  14.     num2++;  
  15.     cout << "num1: " << num1 << endl;  
  16.     cout << "num2: " << num2 << endl;  
  17.     return 0;  
  18. }  



[ 결과 ]



  • 11행 : reffunc 함수가 참조자를 반환했고, 이를 다시 참조자에 저장하고 있다.

  • 13~14행 : 변수 num1과 참조자 num2의 값을 1씩 증가시키고 있다.

  • 15~16행 : 변수 num1과 참조자 num2의 관계를 확인하기 위한 출력이다.


참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가된다. 즉 위의 예제는 다음과 동일하다.


int num1 = 1 ;

int &ref = num1 ;

int &num2 = ref ; 


그런데 함수 reffunc의 매개변수로 선언된 참조자 ref는 지역변수와 동일한 성격을 갖는다. 즉 reffunc이 반환하면 참조자ref는 소멸된다. 그러나 참조자는 참조자일뿐, 그 자체로 변수는 아니다


따라서 참조자가 소멸하더라도 참조자가 참조하는 변수는 소멸되지 않는다.


그럼 이번에는 reffunc의 11행을 다음과 같이 변경해보자.


int num2 = reffunc(num1) ;


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int &reffunc(int &ref) {  
  5.     ref++;  
  6.     return ref;  
  7. }  
  8.   
  9. int main() {  
  10.     int num1 = 1;  
  11.     int num2 = reffunc(num1);  
  12.   
  13.     num1++;  
  14.     num2+=100;  
  15.     cout << "num1: " << num1 << endl;  
  16.     cout << "num2: " << num2 << endl;  
  17.     return 0;  
  18. }  



[ 결과 ]




  • 11행 : 참조형으로 반환이 되지만, 일반 변수를 선언해서 반환 값을 저장할 수 있다.

  • 13~14행 : num1과 num2가 다른 변수임을 확인하기 위해서 서로 다른 연산을 진행하였다.

위 예제에서 보이는 상황은 다음과 동일하다. 다만 함수의 호출을 통해서 이 과정이 진행되었을 뿐이다.

int num1 = 1 ;
int &ref = num1 ;
int num2 = ref ;

이렇듯 반환형이 참조형인 경우, 반환 값을 무엇으로 저장하느냐에 따라서 그 결과에 차이가 있으므로 적절한 선택을 해야 한다. 마지막으로 참조자를 반환하되, 반환형은 기본자료형인 경우를 알아보자.

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int reffunc(int &ref) {  
  5.     ref++;  
  6.     return ref;  
  7. }  
  8.   
  9. int main() {  
  10.     int num1 = 1;  
  11.     int num2 = reffunc(num1);  
  12.   
  13.     num1 += 1;  
  14.     num2 += 100;  
  15.     cout << "num1: " << num1 << endl;  
  16.     cout << "num2: " << num2 << endl;  
  17.       
  18.     return 0;  
  19. }  


[ 결과 ]



  • 6행 : 참조자를 반환하지만, 반환형이 int이기 때문에 참조자가 참조하는 변수 값이 반환된다.

  • 13~14행 : num1과 num2가 다른 변수임을 확인하기 위해 서로 다른 연산을 진행하였다.


실행 결과를 보면 이전의 예제 결과와 차이가 없다. 하지만 다음의 차이가 있다.

반환형이 참조형인 경우 반환 값을 다음과 같이 두가지 형태로 저장할 수 있다.


int num2 = reffunc(num1) ;            (O)

int &num2 = reffunc(num1) ;          (O)


하지만 반환형이 기본 자료형으로 선언된 함수의 반환 값은 반드시 변수에 저장해야 한다.


int num2 = reffunc(num1) ;             (O)

int &num2 = reffunc(num1)            (O)

'Language > C++' 카테고리의 다른 글

C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
C++ 기본(2) - 구조체  (0) 2016.12.23
7. C++ 기본(7)  (0) 2016.12.13
6. C++ 기본(6)  (0) 2016.12.13
5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12

+ Recent posts