10. [C++] 참조자(Reference)와 함수2

2024. 4. 11. 16:01[C++]/C++ 언어 기초

🍃  참조자를 이용한 Call-by-reference의 황당함과 const 참조자

  • 포인터는 잘못 사용할 확률이 높고, 참조자의 활용이 상대적으로 쉽기 때문에 참조자 기반의 함수정의가 더 좋은 선택이라고 생각할 수 있다.
  • 하지만 참조자 기반의 함수정의에 좋은 점만 있는 것은 아니다.
int num = 24;
HappyFunc(num);
cout<<num<<endl;
  • C언어의 관점에서는 100% 24가 출력된다.
  • 그러나 C++에서는 얼마가 출력될 지 알 수 없다. 

함수가 다음과 같이 정의되어 있다면 24가 출력되겠지만,

void HappyFunc(int prm) { . . . . }

 

다음과 같이 정의되어 있다면, 참조자를 이용해 num에 저장된 값을 변경할 수도 있다.

void HappyFunc(int &ref) { . . . . }

 

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

 

이러한 문제점의 해결방안이 무엇일까? 완벽한 해결방안이 되질 못하겠지만, 어느정도 극복할 수 있는 방안이 있다. 

const 키워드의 사용이다.

void HappyFunc(const int &ref) { . . . . }
  • 참조자 ref에 const 선언이 추가되었다. 이는 다음의 의미를 지닌다.
  • "함수 HappyFunc 내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다!"
  • 여기 const 선언으로 인해, 참조자 ref에 값을 저장하는 경우 컴파일 에러가 발생한다.
  • 따라서 함수 내에서 값의 변경이 이뤄지지 않음을 확신할 수 있다.
"함수 내에서, 참조자를 통한 값의 변경을 진행하지 않는 경우, 참조자를 const로 선언해서, 함수의 원형만 봐도 변경이 이뤄지지 않음을 알 수 있게 한다."



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

  • 함수의 반환형에도 참조형이 선언될 수 있다.
int& RefRetFuncOne(int &ref){
    ref++;
    return ref;
}
  • 위 함수에서는 매개변수로 참조자가 선언되었는데, 이 참조자를 그대로 반환하고 있다.
  • 매개변수가 참조자인데, 이를 반환하니 반환형이 참조형인 거구나(이는 잘못된 판단이다.)
int RefRetFuncTwo(int &ref){
    ref++;
    return ref;
}
  • 다음과 같이 참조자를 반환해도 반환형은 참조형이 아닐 수 있다.

RefReturnOne.cpp

#include <iostream>
using namespace std;

int& RefRetFunOne(int &ref) {
    ref++;
    return ref;
}

int main(void) {
    int num1 = 1;
    int &num2 = RefRetFunOne(num1);

    num1++;
    num2++;
    cout <<"num1: "<<num1<<endl;
    cout <<"num2: "<<num2<<endl;
    return 0;
}
num1: 4
num2: 4

 

  • 참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가된다.
  • ref에 저장된 값 4가 아닌, ref라는 참조의 데이터가 반환된다.
  • 따라서 num2는 num1에 대한 참조의 데이터를 전달받는 것이다.

아래와 같이 변경해보자

int &num2 = RefRetFunOne(num1);

 

int num2 = RefRetFunOne(num1);

 

 

RefReturnTwo.cpp

#include <iostream>
using namespace std;

int& RefRetFunOne(int &ref) {
    ref++;
    return ref;
}

int main(void) {
    int num1 = 1;
    int num2 = RefRetFunOne(num1);

    num1+=1;
    num2+=100;
    cout <<"num1: "<<num1<<endl;
    cout <<"num2: "<<num2<<endl;
    return 0;
}
num1: 3
num2: 102

  • 다음과 같이 변경함으로써, num1과 num2는 완전히 별개의 변수가 된다.
  • 참조형(&num2)가 아니라 변수(num2)이어서 참조데이터가 아닌 '값'을 전달 받기 때문이다.

참조자를 반환하되, 반환형은 기본자료형인 경우

RefReturnThree.cpp

#include <iostream>
using namespace std;

int RefRetFunTwo(int &ref) { //반환형이 기본자료형 int이다!
    ref++;
    return ref;
}

int main(void) {
    int num1 = 1;
    int num2 = RefRetFunTwo(num1);

    num1+=1;
    num2+=100;
    cout <<"num1: "<<num1<<endl;
    cout <<"num2: "<<num2<<endl;
    return 0;
}
num1: 3
num2: 102

 

return ref;
  • 이 행을 살펴보면, 참조자를 반환하지만, 반환형이 기본자료형 int이기 때문에 참조자가 참조하는 변수의 값이 반환된다. 다시 한번 말하지만, 변수에 저장된 값이 반환된다.
  • RefReturnTwo.cpp와 실행결과에 차이가 없으나 반환형이 기본자료형으로 선언된 RefReturnTwo 함수의 반환 값은 반드시 변수에 저장해야한다. 반환 값은 상수나 다름없기 때문이다.
  • int num2 = RefRetFunTwo(num1); (o)
  • int &num2 = RefRetFunTwo(num1); (x)

 

🍃 잘못된 참조의 반환

 

int & RetuRefFunc(int n) {
    int num = 20;
    num +=n;
    return num;
}
  • 위 함수에서는 지역변수 num에 저장된 값을 반환하지 않고, num을 참조의 형태로 반환하고 있다.
  • 따라서 다음의 형태로 함수를 호출하고 나면, 지역변수 num에 ref라는 또 하나의 이름이 붙게 된다. 
int &ref = RetuRefFunc(10);
  • 하지만 이게 끝이 아니다. 함수가 반환이 되면, 정작 지역변수 num은 소멸 된다.
  • 따라서 위 처럼 지역변수를 참조형으로 반환하는 일은 없어야 한다.



🍃 const 참조자의 또 다른 특징

const int num = 20;
int &ref = num;
ref+=10;
cout<<num<<endl;
  • 여기서 에러의 원인은 다음 코드이다.
int &ref = num;
  • 이를 허용한다는 것은 ref를 통한 값의 변경을 허용한다는 뜻이 되고, 이는 num을 const로 선언하는 이유를 잃게 만든다.
  • 따라서 다음과 같이 참조자 선언을 해야 한다.
const int num = 20;
const int &ref = num;
  • 이렇게 선언이 되면 ref를 통한 값의 변경이 불가능하기 때문에 상수화에 대한 논리적인 문제점은 발생하지 않는다.
  • 그리고 const 참조자는 다음과 같이 상수도 참조가 가능하다.
const int &ref = 50;

 

 

 

🍃 어떻게 참조자가 상수를 참조하냐고요!

int num = 20+30;
  • 여기서 20, 30은 '리터럴 상수(literal constant)'라 하고 다음의 특징을 지닌다.
  • "임시적으로 존재하는 값이다. 다음 행으로 넘어가면 존재하지 않는 상수다."
  • 그런데, 이러한 상수를 참조한다는 것이 이치에 맞는가? 
const int &ref = 30;
  • 이는 숫자 30이 메모리 공간에 계속 남아있을 때에나 성립이 가능하다.
  • 그래서 C++에서는 위의 문장이 성립할 수 있도록, const 참조자를 이용해 상수를 참조할 때 '임시변수'라는 것을 만든다.
  • 그리고 이 장소에 상수 30을 저장하고선 참조자가 이를 참조하게끔 한다.

  • 사진과 같이 정의된 함수에 인자의 전달을 목적으로 변수를 선언한다는 것은 매우 번거로운 일이다.
  • 그러나 임시변수의 생성을 통한 const 상수참조를 허용함으로써, 위의 함수는 다음과 같이 매우 간단히 호출이 가능해졌다.
cout<<Adder(3, 4)<<endl;
반응형