Software Development/C, C++

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

huiyu 2018. 12. 9. 11:41

* Call-by-value & Call-by-reference

  - Call-by-value : 값을 인자로 전달하는 함수의 호출방식
  - Call-by-reference : 주소 값을 인자로 전달하는 함수의 호출방식


- Call-by-value 함수

1
2
3
4
5
int Adder(int num1, int num2)
{
  return num1+num2;
}
 
cs

 Call-by-value의 형태로 정의된 함수의 내부에서는, 함수외부에 선언된. 변수에 접근이 불가능하다.
 따라서 두 변수에 저장된 값을 서로 바꿔서 저장할 목적으로 다음과 같이 함수를 정의하면 원하는 결과를 얻을 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
 
using namespace std;
 
void SwapByValue(int num1, int num2)
{
  int temp = num1;
  num1=num2;
  num2=temp;
  //Call-by-value
}
 
int main(void)
{
  int val1=10;
  int val2=20;
  SwapByValue(val1, val2);    // val1과 val2에 저장된 값 바뀌기를 기대
  cout<<"val1: "<<val1<<endl// 10 출력
  cout<<"val2: "<<val2<<endl// 20 출력
  return 0;
}
cs
1
2
val1 :  10
val2 :  20
cs


위 결과는 val1과 val2가 값이 바뀌지 않고 있다.
그래서 필요한 것이 다음과 같은 Call-by-reference 기반 함수이다.

두 개의 주소값을 받아서, 그 주소 값이 참조하는 영역에 저장된 값을 직접 변경하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
 
using namespace std;
 
void SwapByRef(int * ptr1, int * ptr2)
{
  int temp = *ptr1;
  *ptr1=*ptr2;
  *ptr2=temp;
  //Call-by-reference
}
 
int main(void)
{
  int val1=10;
  int val2=20;
  SwapByRef(&val1, &val2);    // val1과 val2에 저장된 값 바뀌기를 기대
  cout<<"val1: "<<val1<<endl// 20 출력
  cout<<"val2: "<<val2<<endl// 10 출력
  return 0;
}
cs
1
2
val1 :  20
val2 :  10
cs


* 참조자를 이용한 Call-by-reference

  - call-by-reference의 가장 큰 핵심은 함수 내에서 함수 외부에 선언된 변수에 접근할 수 있다는 것이다.
   이는 참조자를 이용할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
 
using namespace std;
 
void SwapByRef2(int &ref1, int &ref2)
{
  int temp = ref1;
  ref1=ref2;
  ref2=temp;
  //Call-by-reference
}
 
int main(void)
{
  int val1=10;
  int val2=20;
  SwapByRef2(val1, val2);    // val1과 val2에 저장된 값 바뀌기를 기대
  cout<<"val1: "<<val1<<endl// 20 출력
  cout<<"val2: "<<val2<<endl// 10 출력
  return 0;
}
 
cs
1
2
val1 :  20
val2 :  10
cs

-> SwapByRef2()에 선언된 두 참조자 &ref1과 &ref2는 함수 호출시 넘어가는 val1과 val2에 의해 초기화가 진행된다.
  즉, 위의 매개변수 선언은 초기화가 이뤄지지 않은 것이 아니라, 함수호출 시 전달되는 인자로 초기화를 하겠다는 의미의 선언이다.

* SwapByRef2(23, 45); 의 형태로 사용하게 되면 컴파일 에러가 발생한다.
  이는 참조자는 상수를 대상으로 참조할 수 없기 때문이다. (**하단 참조자의 상수 참조를 읽어보기)


* 참조자를 이용한 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) { .... }

-> 함수 HappyFunc 내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다.
  const선언으로 인해, 참조자 ref 에 값을 저장하는 경우 컴파일 에러가 발생한다.
  따라서 함수 내에서 값의 변경이 이뤄지지 않음을 확신할 수 있는 것이다.

* 함수 내에서, 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서,
 함수의 원형만 봐도 값의 변경이 이뤄지지 않음을 알 수 있게 한다.


* 반환형이 참조자인 경우
  

1
2
3
4
5
int& RefRetFuncOne(int &ref)
{
  ref++;
  return ref;
}
cs

int num1 = 1;
int &num2 = RefRetFuncOne(num1);
num1++;
num2++;

->num1과 num2의 출력은 모두 4로 나타난다.
 이는 참조자인 num2가 함수 호출을 통해 num1를 참조하고 있기 때문이다.


아래와 같이 num2를 일반 변수로 호출하게 되면,

  int num1 = 1;
  int num2 = RefRetFuncOne(num1);
  num1+=1;
  num2+=100;

-> 결과는 num1은3, num2는 102가 출력된다.
  이는 num2는 참조자가 아닌 함수 호출을 통에 단순 반환값을 저장하는 변수이기 때문이다.

* 이렇듯 반환형이 참조형인 경우, 반환 값을 무엇으로 저장하느냐에 따라서 그 결과에 차이가 있다.


* 잘못된 참조 반환

1
2
3
4
5
6
int& RetuRefFunc(int n)
{
  int num = 20;
  num+=n;
  return num;
}
cs

 -> 위의 함수에서는 지역변수 num에 저장된 값을 반환하지 않고, 참조의 형태로 반환하고 있다.
  따라서 다음의 형태로 함수를 호출하고 나면,

int &ref=RetuRefFunc(10);

지역변수 num에 ref라는  또 하나의 이름이 붙게 된다. 하지만 함수가 반환이 되면, 함수의 지역변수인 num은 소멸된다.
컴파일러는 경고메시지만 띄울 뿐, 에러메시지를 띄워주지 않는다.


* const 참조자

  const int num = 20;
  int &ref = num;
  ref+=10;
  cout<<num<<endl;

-> const 선언을 통해서 num을 상수화했는데, 참조자 ref를 통해서 값을 변경하고 있다.
  이를 허용한다면 num의 상수화는 의미가 없다. 그래서 C++은 이를 허용하지 않고 있다.
  따라서 상수화된 변수에 대한 참조자 선언은 다음과 같이 해야한다.

  const int &ref=num;


* 참조자의 상수 참조

  참조자는 변수만 참조가 가능하고 상수는 참조가 불가능하다.
  그러나 const를 선언한 참조자는 아래와 같이 사용가능하다.

  const int &ref = 50;

  -> int num = 20+30;
    여기서 20, 30과 같은 프로그램상에서 표현되는 숫자를 가리켜 '리터럴(literal)' 또는 '리터럴 상수(literal constant)'라 한다.
    이들은 임시적으로 존재하는 값으로, 다음 행으로 넘어가면 존재하지 않는 상수이다.

  덧셈 연산을 위해서는 20, 그리고 30 둘다 메모리 공간에 저장되어야 한다. 하지만 저장되었다고 해서 재 참조가 가능한 값은 아니다.
  즉, 다음 행으로 넘어가면 소멸되는 상수라고 해도 틀리지 않는다.

 const int &ref = 30;
 -> 이는 숫자 30이 메모리 공간에 계속 남아있을 때에나 성립이 가능한 문장이다.
   그래서 C++에서는 위의 문장이 성립될 수 있도록, const 참조자를 이용해서 상수를 참조할 때 '임시 변수'라는 것을 만든다.
   이 장소에 상수 30을 저장하고선 참조자가 이를 참조가능하게 한다.


* '임시변수'를 이용해 상수 참조를 가능하게 한 이유

1
2
3
4
int Adder(const int&num1, const int &num2)
{
  return num1 + num2;
}
cs

 -> 위와 같이 정의된 함수에 인자의 전달을 목적으로 변수를 선언한다는 것은 매우 번거롭다.
  임시변수의 생성을 통한 const 참조자의 상수참조를 허용함으로써, 위의 함수는 다음과 같이 호출이 가능하다.
  Adder(3, 5);


728x90

'Software Development > C, C++' 카테고리의 다른 글

[C++] 클래스(Class)와 객체(Object)  (0) 2018.12.15
[C++] C++에서의 구조체  (0) 2018.12.15
[C++] 참조자(Reference)의 이해  (0) 2018.12.09
[C++] namespace & using  (0) 2018.12.02
[C++] 인라인(inline) 함수  (0) 2018.12.02