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

2024. 9. 14. 01:37·공부/C++
728x90

*윤성우의 열혈 C++ 프로그래밍 교재로 학습하며 정리하였습니다.

*이번 파트는 내용도 많고 어렵게 느껴져서 복습 차원에서 쭉 정리해봤습니다...

 

Call-by-value & Call-by-reference

Call-by-value

Call-by-value기반의 함수는 값을 인자로 전달하는 함수의 호출방식이다.

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);
    cout << "val1 : " << val1 << endl;//10 출력
    cout << "val2 : " << val2 << endl;//20 출력
    //val1과 val2에 저장된 값이 서로 바뀌지 않았다.
}

Call-by-value의 형태로 정의된 함수에 내부에서는 함수 외부에 선언된 변수에 접근이 불가능하다.

따라서 위의 코드에서 val1과 val2의 값은 서로 바뀌지 않는다.

 

 

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

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);
    cout << "val1: " << val1 << endl;//20 출력
    cout << "val2: " << val2 << endl;//10 출력
    //val1과 val2에 저장된 값이 서로 바뀌었다.
    return 0;
}

Call-by-reference의 형태로 정의된 함수의 내부에서는 함수 외부에 선언된 변수에 접근이 가능하다.

따라서 위의 코드에서 val1과 val2의 값이 서로 바뀐 것을 확인할 수 있다.

 

 


Call-by-address? Call-by-reference!

C언어에서는 함수 외부에 선언된 메모리 공간에 접근하는 방식이 주소 값을 전달하는 방법 뿐이었지만,

C++에서는

1. 주소값을 전달하는 방법  2. 참조자를 이용하는 방법

위의 두가지 방법이 존재한다.

 

int num1 = 10, num2 = 20;
SwapByRef(&num1, &num2);

위의 코드에서는 num1과 num2의 주소값을 전달했으므로 Call-by-reference형태임을 확실하게 알 수 있다.

int num1 = 10, num2 = 20;
int* p1 = &num1;
int* p2 = &num2;

SwapByRef(p1, p2);

num1과 num2의 입장에서 SwapByRef 함수는  Call-by-reference라고 할 수 있을것이다.

그러나 p1과 p2의 입장에서도 SwapByRef 함수가 Call-by-reference라고 할 수 있을까?

=> 위의 코드에서는p1과 p2의 값(== num1과 num2의 주소값)을 전달하는것이지, 

p1과 p2의주소 값을 전달하는 것이 아니기 때문에 p1과 p2의 입장에서는 Call-by-reference라고 할 수 없을 것이다.

 

결론은 Call-by-reference 형태는 주소 값이 전달되었다는 사실보다는

"주소 값을 전달받아서, 함수 외부에 선언된 변수에 접근하는 형태"

라는 것이 더 중요하다는 사실이다.

 


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

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의 값이 전달되는 것이 아니라,
    //val1과 va2를 참조하게 된다.
    //va1과 ref1은 같은 메모리 공간에 붙은 이름이지만,
    //val1은 main 스택에, ref1는 SwapByRef2()의 스택에 각각 존재한다..
    cout << "val1 ; " << val1 << endl;
    cout << "val2 : " << val2 << endl;
    return 0;

참조자는 선언과 동시에 초기화 되어야 한다.  

  위의 코드에서 ref1과 ref2는 선언과 동시에 초기화되지 않는 것처럼 보이지만
    ref1과 ref2는 SwapByRef2가 호출되는 순간에 선언되고, SwapByRef2 함수의 인자로 받는 값으로 초기화가 이루어진다.
    따라서 ref1과 ref2는 선언과 동시에 초기화되므로 참조자로서 문제가 되지 않는다.

 

SwapByRef2함수를 빠져나간 뒤에는 참조자인 ref1,ref2가 소멸되고,

val1,val2는 그대로 남아있게 된다.

 

매개변수로 선언된 참조자 ref1,ref2는 main함수에 선언된 변수 val1,val2의 또 다른 이름이 된다.

SwapByRef2 함수 내에서는 참조자 ref1,ref2를 통해서 값의 교환이 이루어지므로,

그 결과 실제로 val1과 val2의 값의 교환이 이루어진다.

 

이처럼 C++에서는 포인터를 사용하는 방법 뿐만 아니라, 참조자를 통해서도 Call-by-reference형태를 이루는 것이 가능하다.

 


 참조자를 이용한 Call-by-reference의 단점과 const 참조자

참조자를 이용한 Call-by-reference의 존재로 인한 단점도 존재한다.

함수의 호출문만 봐서는 Call-by-reference인지 Call-by-value인지 판단이 불가능하다는 점이다.

 매개변수가 어떻게 선언되었는지에 대한 확인이 필수적이다.

int num = 24;
HappyFunc(num);
cout << num << endl;
//얼마가 출력되는지 알 수 없다.

void HappyFunc(int ref) {   }
//위와 같이 정의되어있다면 24가 출력된다.

void HappyFunc(int& ref) {   }
//위와 같이 정의되어있다면 참조자를 통해 num에 저장된 값이 변경될 수도 있다.

const 참조자

위와 같은 단점을 극복하기 위해 const 키워드를 이용할 수 있다.

void HappyFunc(const int& ref) {   }

참조자 ref에 const선언이 추가되면

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

라는 의미를 가진다.

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

참조자를 const로 선언하여 함수의 원형만 봐도 값의 변경이 이뤄지지 않음을 알 수 있게 하는 것이 좋다.

 


1, 반환형이 참조형(Reference Type)이고, 반환도 참조로 받는 경우

함수의 반환형으로도 참조형이 선언 될 수 있다.

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

int main(void) {
    int num1 = 1;
    int &num2 = RefRetFunOne(num1);
    //num2의 자료형도 int&로 한다.
    //num2도 num1을 가리키는 참조자이다.

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

위의 코드에서 반환형이 참조자이므로 ref 참조자 자체를 반환한다.

참조형으로 반환된 값을 참조자 num2에 저장하면,

num1의 메모리 공간에 대한 참조자 num2가 추가되는 것이다.


2. 반환형이 참조형이지만, 일반 변수로 반환 값을 저장하는 경우

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; //3
    cout << "num2: " << num2 << endl; //102
    return 0;
}

 

참조형으로 반환을 하지만, 참조자가 아닌 일반 변수에 반환 값을 저장하게 되면,

main함수 내의 num1과 num2는 서로 관련이 없는 완전한 별개의 변수가 된다.

 

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

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

int main(void) {
    int num1 = 1;
    int num2 = RefRetFunTwo(num1);
    //참조형으로 반환이 되지만, 참조자가 아닌 일반변수를 선언해서
    //반환 값을 저장할 수도 있다.
    //num1과 num2는 아무 관계도 없는 별개의 변수이다.

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

참조자를 반환하지만, 반환형이 기본 자료형 int이기 때문에 참조자가 참조하는 변수의 값이 반환된다.

num1과 num2는 완전히 서로 다른 변수이다.


잘못된 참조의 반환

int& RetuRefFunc(int n) {
    int num = 20;
    num += n;
    return num;
}

위의 함수는 지역변수 num을 참조 형태로 반환하고 있다.

int& ref = RetuRefFunc(10);

위의 형태로 함수를 호출하고 나면, 지역변수 num에 ref라는 또 하나의 이름이 붙게 된다.

그러나 함수가 반환이 되고 나면 지역변수 num은 소멸된다.

따라서 지역 변수를 참조형으로 반환하면 안된다.


const참조자의 또 다른 특징

const int num = 20;
int& ref = num; //<- 컴파일 에러 발생!
ref += 10;
cout << num << endl;

const 선언을 통해서 변수 num을 상수화했는데,

참조자 ref를 통해서 값을 변경할 수 있다면 const 선언의 의미가 없어진다
따라서 C++에서는 이를 허용하지 않는다.

const int num = 20;
const int& ref = num;

 

위와 같이 선언 되면 ref를 통한 값의 변경이 불가능하기 때문에 상수화에 대한 논리적인 문제점은 발생하지 않는다.

const int& ref = 50;

const 참조자는 위와 같이 상수도 참조 가능하다!


참조자가 상수를 참조할 수 있는 이유

int num = 20 + 30;

위의 코드에서 20,30과 같은 프로그램상에서 표현되는 숫자를 '리터럴 상수(literal constant)'라고 한다.
리터럴은 일시적으로 존재하는 값이며, 다음 행으로 넘어가면 존재하지 않는 상수이다.

const int& ref = 30;

그러나 위 문장은 숫자 30이 메모리 공간에 계속 남아있어야 성립되는 문장이다.

C++에서는 const 참조자를 통해서 상수를 참조할 때, '임시변수'라는 것을 만든다.

그리고 임시변수에 상수 30을 저장하고, 참조자가 이를 참조하도록 한다.

 

int Adder(const int& num1, const int& num2) 
{
    return num1 + num2;
}

임시 변수의 생성을 통한 const 참조자의 상수 참조를 허용함으로써, 위의 함수는 다음과 같이 간단하게 호출할 수 있다.

cout << Adder(3, 4) << endl;

 

728x90

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

[C++] C++에서 C언어의 표준 함수 호출하기  (0) 2024.09.15
[C++] 2-5. new & delete  (1) 2024.09.15
[C++] 2-3. 참조자의 이해  (0) 2024.09.11
[C++] 2-2. 새로운 자료형 bool  (0) 2024.09.11
[C++] 2-1. C언어 복습  (0) 2024.09.11
'공부/C++' 카테고리의 다른 글
  • [C++] C++에서 C언어의 표준 함수 호출하기
  • [C++] 2-5. new & delete
  • [C++] 2-3. 참조자의 이해
  • [C++] 2-2. 새로운 자료형 bool
knhoo
knhoo
  • knhoo
    &*
    knhoo
  • 전체
    오늘
    어제
    • 전체 (144) N
      • Unity 개발일지 (20)
        • [Unity2D]졸업프로젝트 (17)
        • [Unity3D]VR프로젝트 (2)
      • 공부 (119) N
        • 게임 수학 (1)
        • 부트캠프 (13) N
        • C++ (39)
        • Unity & C# (8)
        • 데이터베이스 (2)
        • 컴퓨터비전 (0)
        • 컴퓨터구조 (0)
        • python (7)
        • BAEKJOON (38)
        • 개발 (2)
        • 자료구조 (9)
      • 일상 (2)
  • 블로그 메뉴

    • Github
    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • 📖README
  • 인기 글

  • 태그

    unity2d
    비트버니
    멋쟁이사자처럼후기
    오블완
    백준
    앱테크
    C++
    구간합
    til
    캐시워크
    머니워크
    unity
    자료구조
    티스토리챌린지
    Cpp
    야핏무브
    c#
    패널파워
    백준 #python
    Python
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
knhoo
[C++] 2-4. 참조자(Reference)와 함수
상단으로

티스토리툴바