728x90

참조 변수 (Reference variable)
C언어에서 일반 변수와 포인터 변수에 대해서 학습했는데 변수란 메모리 공간에 할당된 이름이다. 그 이름을 통해 해당 메모리 공간에 접근한다. C언어에서는 하나의 메모리 공간에 하나의 이름만 부여할 수 있다.

하지만 C++ 에서는 reference 를 선언하게 되면, "이름이 존재하는 공간에 하나의 이름을 더 부여" 할 수 있다.


reference 를 참조 변수 또는 참조자 라고 표현한다. 책에 따라 우리말 명칭이 약간씩 다름을 알자.


reference는 C++에서 지원하는 변수 타입으로 다른 객체 또는 값의 별칭으로 사용된다.

int a(10); // C언어의 int a = 10; 과 같다.

int &ref = a; // 선언 방법은 자료형 &참조변수이름 = 원래변수;

non-const 값 참조형은 자료형 뒤에 앰퍼샌드(&)를 사용하여 선언한다.
Lvalue는 메모리 주소를 가진 객체이고 Rvalue는 메모리 주소가 없고, 표현식 범위에만 있는 임시 값이다.

참조변수에 대입하는 값은 Lvalue 만 가능하다.

ㅇ 주소 연산자 & 는 Lvalue를 요구하기 때문에 표현식이 Rvalue라면 컴파일 오류가 발생한다.

C++에서 모든 표현식은 Lvalue 또는 Rvalue 이다.
Lvalue는 객체를 참조하는 표현식이다. 메모리 위치를 가지고 있다. const 타입을 포함한 모든 변수는 Lvalue 이다.
Rvalue는 표현식이 종료된 이후에는 더이상 존재하지 않는 임시적인 값이다. 상수 또는 임시 객체는 Rvalue 이다.

ㅇ 단항 & (address-of) 연산자는 그것의 피연산자로 Lvalue 를 필요로 한다.

    int n, *p;
    p = &n;     // Fine. &n는 n이  Lvalue 인 경우에만 유효한 표현식이다. 
    &n = p;     // Error: &n is an Rvalue

ㅇ 포인터 p 는 항상 객체를 가리킨다. 그러므로 *p 는 Lvalue 이다.

    int a[N];
    int *p = a;
    *p = 3;      // Fine.

    *(p + 1) = 4; // Fine. (p+1) 는 Rvalue

ㅇ 리턴타입이 오직 참조인 경우에만 함수 호출은 Lvalue 이다.
    int& GetBig(int& a, int& b) {    // 함수 호출을 Lvalue 로 만들기 위해 참조를 반환
        return ( a > b ? a : b );
    }


출처 : http://jeremyko.blogspot.com/2012/08/lvalue-rvalue.html 에서 필요 부분만 발췌

원출처 : https://www.codeproject.com/Articles/313469/The-Notion-of-Lvalues-and-Rvalues


참조자는 별도의 메모리를 할당하지 않고 변수 선언문에서 초기화에 사용한 변수의 메모리를 참조하여 사용한다. 이러한 이유로 참조자는 선언과 동시에 무조건 변수를 참조하도록 해야 한다.

null 값을 저장할 수 있는 포인터와 다르게, null 참조 같은 것은 없다.


배열의 요소는 변수로 간주되어 참조자 선언이 가능하다.

int arr[3] = { 1, 2, 3};

int &ref1 = arr[0];

int &ref2 = arr[1];

int &ref3 = arr[2];


포인터 변수도 변수이기 때문에 참조자의 선언이 가능하다.

int num = 10;

int *ptr = #

int &ref = num;

int *(&pref) = ptr;


예제1.

#include <iostream>

using namespace std;

void swap(int& a, int& b) { // 함수의 매개변수를 참조변수(reference)로 선언하는 방식
    // 참조변수(reference)로 매개변수를 받았다

    // 포인터 연산을 할 필요가 없으므로 보다 안정적이다.

    // 함수의 호출 형태를 구분하기 어렵다.
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int a(10);
    int b(5);

    swap(a, b);

    std:cout << "두 수의 값을 바꾸는 함수" << std::endl;
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};

    for (int n : arr) {
        cout << n << ' ';
        n++;
    }
    cout << endl;

    // reference 변수

    // 자료형 &참조변수이름 = 원래변수;
    int &ref = a; // 변수를 입력받는다. 상수는 안된다.
    ref = 20;

    cout << "ref : " << ref << endl;
    cout << "a : " << a << endl;

    ref++;
    // reference 연산은 reference가 참조하는 변수의 이름으로 하는 연산과 효과가 똑같다.
    cout << "ref : " << ref << endl;
    cout << "a : " << a << endl;

    string food = "Pizza"; // food 변수
    string &meal = food; // food 의 reference

    cout << endl;
    cout << food << endl;  // Pizza 출력
    cout << meal << endl;  // Pizza 출력

    string* ptr = &food; // 포인터 선언 (주소값을 입력 받는다)

    cout << endl;

    // food의 메모리 주소와 reference 메모리 주소가 동일하고, 할당한 포인터 주소가 동일함을 확인할 수 있다.

    // 하지만 포인터 자체 주소는 다르다는 것도 알 수 있다.

    cout << &food << endl;  // food 값의 메모리 주소 출력
    cout << &meal << endl;  // refernece 값의 메모리 주소 출력
    cout << ptr << endl;    // 포인터의 메모리 주소 출력
    cout << &ptr << endl;    // 포인터 자체 메모리 주소 출력
    cout << *ptr << endl; // 역참조 연산자(*)로 포인터가 가리키는 값 출력

}


예제2

#include <iostream>
using namespace std;

int& increment(int& val) { // 참조 변수로 원본을 받음
    val++;
    return val;
}

int main() {

    int n = 10;
    int& ref = increment(n); // 파라미터로 reference 원본 넘겨줌

    cout << "n : " << n << endl;
    cout << "ref: " << ref << endl;

    cout << "n 주소값 : " << &n << endl;
    cout << "ref 주소값 : " << &ref << endl;
}

원본 변수 n 은 참조 변수 ref가 사용되는 동안 유지되기 때문에 결과는 11을 반환한다.

반환이 int& 인데 int 로 하면 어떻게 되는지 변경해보라.

같은 주소를 사용하는지 다른 주소를 사용하는지 확인이 될 것이다.


그런데 return 받는 변수가 지역변수라면 어떤 결과가 발생할까?

https://www.youtube.com/watch?v=Bko1OoExWhg&t=319s 동영상 강좌를 참조해서 들어보자.


#include <iostream>
using namespace std;

int &CalSum(int s, int e) {
    int sum = 0;
    for (; s <= e; ++s) {
        sum += s;
        cout << " sum : " << sum << endl;
    }

    cout << " sum address : " << &sum << endl;
    return sum;
}

int main() {

    int &ref1 = CalSum(1, 20);
    int &ref2 = CalSum(1, 10);

    // cout << endl; // 이 한줄을 넣으면 결과가 엉뚱하게 나온다.
    cout << " ref1: " << ref1 << ", ref2: " << ref2 << endl;

    return 0;
}

실행 결과

C++ 에서 함수 처리 return 메커니즘은 return 값을 임시 저장 공간에 복사한 후 그것을 return하는 방식이다.
하지만 return by reference 형식을 사용할 경우 임시 저장공간을 사용하지 않고 호출 함수가 return값에 직접 접근을 하게 된다.


main()이 실행되면서 int ref1 과 int ref2 에 메모리를 각각 4 바이트씩 할당한다.

그 다음에 CalSum() 함수가 실행되면 함수 내 지역변수 int sum 에 메모리를 할당하기 위해 추가적으로 4바이트를 할당한다.


CalSum(1, 20) 의 결과 리턴으로 210을 반환한다. 그런 다음에 CalSum()함수가 종료되고, 지역변수 sum 이 차지하는 메모리 공간은 사라진다.

CalSum(1, 10)을 실행하면서 다시 지역변수 sum 이 차지하는 메모리 공간을 할당한다. 할당된 공간의 메모리 주소는 동일한 주소 공간을 차지한다는 걸 위 실행 결과에서 확인할 수 있다.

여기서 ref1 도 동일한 메모리 주소를 참조하고 있기 때문에 결과는 210이 아닌 55를 반환한다.


그 이유는 함수 내의 로컬 변수의 reference를 참조할 경우, local 변수는 함수 종료시 파괴되지만 ref1의 경우 reference 자체를 계속해서 참조하고 있기 때문이다.


따라서 지역변수를 reference 로 return 하는 경우 원하지 않는 결과를 초래할 수 있으므로 코딩시 주의해야 한다.

728x90

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

C++ namespace(네임스페이스)  (0) 2019.11.22
C++ 오버로딩  (0) 2019.11.22
C++ 입력(cin) 과 출력(cout)  (0) 2019.11.21
C언어 중첩 구조체(Nested structure)  (0) 2019.11.20
C언어 구조체(Struct) 이해 및 C++ 구조체 예제  (0) 2019.11.19
블로그 이미지

Link2Me

,