소년코딩

참조형 변수 (Reference variable)

지금까지 두 가지 변수 타입을 공부했다.

  • 일반 변수(normal variable): 직접 값을 보유.
  • 포인터(pointer): 다른 값의 주소(또는 null)를 보유.

참조형(reference)은 C++에서 지원하는 세 번째 변수 타입이다. 참조형은 다른 객체 또는 값의 별칭으로 사용되는 C++ 타입이다.

C++은 세 가지 종류의 참조형을 지원한다.

  1. non-const 값 참조형
  2. const 값 참조형
  3. r-value 참조형

References to non-const values

non-const 값에 대한 참조형은 자료형 뒤에 앰퍼샌드(&)를 사용하여 선언한다.

  • 자료형& 별명 = 기존 변수명;
int value = 5; // normal integer
int& ref = value; // reference to variable value

위 코드에서 &는 주소(address)를 의미하지 않고 참조(reference)를 의미한다.


References as aliases

참조형은 참조하는 값과 동일하게 작동한다. 이런 의미에서 참조형은 참조되는 객체의 별칭으로 사용된다.

#include <iostream>

int main()
{
    int value = 5; // normal integer
    int& ref = value; // reference to variable value

    value = 6; // value is now 6
    ref = 7;   // value is now 7

    std::cout << value; // prints 7
    ++ref;
    std::cout << value; // prints 8

    return 0;
}

// 7
// 8

위 예제에서 refvalue는 동의어로 취급된다.

참조형에 주소 연산자(&)를 사용하면 참조되는 값의 주소가 반환된다.

cout << &value; // prints 0012FF7C
cout << &ref;   // prints 0012FF7C

A brief review of l-values and r-values

'01.02 - 변수, 초기화 및 할당' 포스트에서 l-value와 r-value를 공부한 적이 있다.

요약하면, l-value는 메모리 주소를 가진 객체이고 r-value는 메모리 주소가 없고, 표현식 범위에만 있는 임시 값이다.


References must be initialized

참조형은 선언과 동시에 반드시 초기화해야 한다.

int value = 5;
int& ref = value; // valid reference, initialized to variable value

int& invalidRef; // invalid, needs to reference something

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

non-const 값에 대한 참조는 non-const 값으로만 초기화할 수 있다. const 값 또는 r-value로 초기화할 수 없다.

int x = 5;
int& ref1 = x; // okay, x is an non-const l-value

const int y = 7;
int& ref2 = y; // not okay, y is a const l-value

int& ref3 = 6; // not okay, 6 is an r-value

References can not be reassigned

초기화된 후에는 다른 변수를 참조하도록 변경할 수 없다.

int value1 = 5;
int value2 = 6;

int& ref = value1; // okay, ref is now an alias for value1
ref = value2; // assigns 6 (the value of value2) to value1 -- does NOT change the reference!

References as function parameters

참조형은 함수 매개 변수로 가장 많이 사용된다. 이때 매개 변수는 인수의 별칭으로 사용되며, 복사본이 만들어지지 않는다. 이렇게 하면 복사하는데 비용이 많이 드는 겨우 성능이 향상될 수 있다.

함수에 포인터 인수 전달하면 함수 안에서 포인터를 역참조하여 인수의 값을 직접 수정할 수 있었다. 이런 점에서 참조형은 유사하게 작동한다. 참조형 매개 변수는 인수의 별칭으로 사용되므로 참조 매개 변수를 사용하는 함수는 전달된 인수를 수정할 수 있다.

#include <iostream>

// ref is a reference to the argument passed in, not a copy
void changeN(int& ref)
{
    ref = 6;
}

int main()
{
    int n = 5;

    std::cout << n << '\n';

    changeN(n); // note that this argument does not need to be a reference

    std::cout << n << '\n';
    return 0;
}

// 5 
// 6

인수 n이 함수에 전달되면 함수 매개변수 ref가 인수 n에 대한 참조로 설정된다. 이것은 함수가 ref를 통해 n의 값을 변경할 수 있게 한다. 변수 n 자체가 참조형일 필요는 없다.


Using references to pass C-style arrays to functions

C 스타일 배열에서 가장 귀찮은 문제 중 하나는 대부분이 평가될 때 포인터로 변환된다는 점이다. 그러나 C 스타일 배열을 참조로 전달하면 이러한 변환이 발생하지 않는다.

#include <iostream>

// Note: You need to specify the array size in the function declaration
void printElements(int (&arr)[4])
{
  int length{ sizeof(arr) / sizeof(arr[0]) }; // we can now do this since the array won't decay

  for (int i{ 0 }; i < length; ++i)
  {
    std::cout << arr[i] << std::endl;
  }
}

int main()
{
    int arr[]{ 99, 20, 14, 80 };

    printElements(arr);

    return 0;
}

이 작업을 수행하려면 명시적으로 매개 변수에서 배열의 크기를 정의해야 한다.


References as shortcuts

참조형의 또 다른 장점은 중첩된 데이터에를 쉽게 접근할 수 있게 한다는 것이다.

struct Something
{
    int value1;
    float value2;
};

struct Other
{
    Something something;
    int otherValue;
};

Other other;

other.somthing.value1에 접근할 경우 타이핑 양이 많아지고 여러 개면 코드가 엉망이 될 수 있다. 참조를 통해 좀 더 쉽게 접근할 수 있다.

int& ref = other.something.value1;
// ref can now be used in place of other.something.value1

따라서 다음 두 명령문은 같다.

other.something.value1 = 5;
ref = 5;

이렇게 하면 코드가 더 명확하고 읽기 쉽게 유지할 수 있다.


References vs pointers

참조형과 포인터는 흥미로운 관계에 있다. 참조형은 접근할 때 암시적으로 역참조되는 포인터와 같은 역할을 한다. (참조형은 내부적으로 포인터를 사용하여 컴파일러서 구현된다.)

int value = 5;
int* const ptr = &value;
int& ref = value;

*ptrref는 동일하게 평가된다. 그러므로 다음 두 명령문은 같은 효과를 낸다.

*ptr = 5;
ref = 5;

참조형은 선언과 동시에 유효한 객체로 초기화해야 하고, 일단 초기화되면 변경할 수 없으므로 포인터보다 사용하는 것이 훨씬 안전하다. (널 포인터를 역참조하면 위험하다.)

주어진 문제가 참조형과 포인터 둘 다로 해결할 수 있다면 참조형을 사용하는게 더 좋다.


요약 (Summary)

참조형을 통해 다른 객체나 값에 대한 별칭을 정의할 수 있다. non-const 값에 대한 참조는 non-const 값으로만 초기화할 수 있다. 일단 초기화되면 참조를 재할당할 수 없다.

참조형은 인수의 값을 수정하려는 경우나 인수의 비싼 복사본을 만들지 않으려는 경우 함수 매개변수로 자주 사용된다.


cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/611-references/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기