프로그래밍/C++

C++ 참조형 변수 (Reference variable)

Rozentea 2024. 1. 29. 17:18
해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~

1. 참조형 변수 (Reference variable)


C++에서는 일반 변수 자료형, 포인터 자료형 뿐 아니라 참조형(reference)를 지원한다.

참조형은 다른 객체 또는 값의 별칭으로 사용되는 C++타입이다.

 

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

1. non-const 값 참조형

2. const 값 참조형

3. r-value 참조형

 

2. 참조형 정리


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

int iValue = 10;

// 자료형& 변수명(별명) = 기존 변수명
int& refValue = iValue;

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

 

이러한 참조형은 참조를 하는 값과 동일하게 작동한다.

iValue = 7;	// iValue는 10 -> 7로 변경된다.
refValue = 2;	// iValue는 7 -> 2로 변경된다.

즉, iValue와 refValue는 동의어로 취급된다.

int* address = &iValue;		// iValue의 주소
int* address2 = &refValue;	// 위와 동일한 iValue의 주소

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

 

float A;
float* addA;
float& refA; // 불가능

참조형을 사용할 때 주의할 점은 다른 자료형과 달리 선언만 할 수 없다는 것이다.

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

(생각해보면 위에서 살펴본 것 처럼 참조형은 참조하는 값과 동일하게 되는데, null과 동일하게 되는게 말이 안된다 생각한다.)

float A;
float& refA = A;

따라서 참조형은 반드시 선언과 초기화를 함께 해주어야 한다.

 

int& refTest1 = 5; // 불가능
const int iTest2 = 1;
int& refTest2 = iTest2; // 불가능

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

 

int iValue = 10;
int& refValue = iValue;

int iValue2 = 100;
refValue = iValue2;

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

위 코드의 경우 실행하면 refValue는 100이고 iValue에도 100이 들어간다.

참조형을 선언하고 초기화를 해주면 해당 참조형 변수는 들어온 변수와 동일 취급이 되기 때문에 이후 대입 연산자는 참조를 변경하는 것이 아니라 참조된 변수에 값을 대입하는게 되어버린다.

 

참조형은 함수 매개 변수로 가장 많이 사용된다.

이때 매개 변수는 인수의 별칭으로 사용되며, 복사본이 만들어지지 않는다.

-> 매개 변수로 주소를 사용하게 되면, 복사본이 만들어지기 때문에 주소의 대상이 큰 경우 복사본의 크기가 커지게 된다.

이렇게 하면 복사하는데 비용이 많이 드는 경우 성능이 향상될 수 있다.

 

함수에 매개 변수로 포인터를 사용하면 함수 안에서 포인터를 역참조해 인수 값을 직접 수정할 수 있었다.

이런 점에서 참조형은 포인터와 유사하게 작동한다.

참조형 매개 변수는 인수의 별칭으로 사용되므로 참조 매개 변수를 사용하는 함수는 전달 받은 인수를 직접 수정할 수 있다.

#include <iostream>

void changeN(int& ref)
{
    ref = 6;
}

int main()
{
    int n = 5;

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

    changeN(n);

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

 

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

struct Something
{
	int ivalue1;
    float fvalue2;
}

struct Other
{
	Something something;
    int otherValue;
}

Other other;

int& ref = other.something.ivalue1;

other.somoething.ivalue1 = 5;
ref = 5;

위 코드에서 아래의 두 코드는 같은 명령문이다.

이렇게 하면 타이핑 양도 줄일 수 있고, 코드가 더 명확하고 읽기 쉽게 유지할 수 있다.

 

참조형과 포인터는 흥미로운 관계에 있다.

참조형은 접근할 때 암시적으로 역참조되는 포인터와 같은 역할을 한다.

(참조형은 내부적으로 포인터를 사용하여 컴파일러에서 구현된다.)

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

*ptr = 5;
ref = 5;

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

 

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

 

이전 프로젝트들을 진행할 때 참조형은 복사 비용을 줄이기 위한 함수의 매개 변수로 사용하는 경우가 대부분이었다.

앞으로 프로젝트를 진행할 때는 해당 부분을 참고해 포인터가 아니라 참조형을 사용하는 방향을 고려해야겠다.

 

3. 참고 문헌

 

C++ 07.15 - 참조형 변수 (Reference variable)

참조형 변수 (Reference variable) 지금까지 두 가지 변수 타입을 공부했다. 일반 변수(normal variable): 직접 값을 보유. 포인터(pointer): 다른 값의 주소(또는 null)를 보유. 참조형(reference)은 C++에서 지원하

boycoding.tistory.com