c++의 참조변수 (reference variable)
🎯레퍼런스
- 참조
- 레퍼런스를 이용하여 다른 변수를 참조하는 변수를 만들 수 있습니다.
- 참조를 하게되면 해당 변수의 값을 변경할 수 있게 됩니다.
- 참조 대상은 반드시 레퍼런스 선언과 동시에 초기화(지정)되어야 합니다.
- 레퍼런스 선언 이후 대상 변경은 불가능합니다.
- 모든 타입은 레퍼런스로 만들 수 있습니다.
- 참조하게 되는 변수의 별칭, 또다른 이름, 별명 이라고 생각하면 이해가 쉽습니다.
- 포인터와 다르게 참조하는 변수의 주소를 저장하고 있진 않습니다.
- 선언시 해당 변수를 참조한다고 컴파일러에게 알려줍니다
- 따로 메모리공간을 할당하지 않습니다
- 해당 변수의 별칭만 한 개 더 생기게 됩니다.
✅ 예제1
int number = 500;
// refNumber는 number 변수를 참조하게 되어
// refNumber를 이용하여 number의 값을 변경할 수 있게 된다.
int& refNumber = number;
refNumber = 100;
std::cout<< number << std::endl; // 100 출력
✅ 예제2
int number = 500;
int number2 = 50;
int& refNumber = number;
// refNumber가 참조하는 number에 number2의 값을 "대입"
refNumber = number2;
std::cout << number << std::endl; // 50 출력
refNumber = 100;
std::cout << number << std::endl; // 100 출력
std::cout << number2 << std::endl; // 50 출력
참조된 변수는 초기화 이후 변경할 수 없습니다.
refNumber = number2;는 단순히 refNumber가 참조하는 number에 number2의 값을 대입하게 됩니다.
✅ 예제3 : 함수의 인자로 구조체 또는 클래스 레퍼런스로 사용
struct FItem
{
char name[32];
EItemType itemType;
bool isActive = true;
int option = 0;
};
void Print( FItem& item )
{
// 출력용 함수에서 레퍼런스 타입 변수의 값을 마음대로 바꿀 수 있음
item.option = 1;
// ...
}
int main()
{
FItem item;
strcpy_s( item.name, "목검" );
item.itemType = EItemType::weapon;
item.isActive = true;
item.option = 0;
FItem& refItem = item;
Print( refItem );
return 0;
}
위 예시에서 함수에 레퍼런스 인자로 받을 경우
레퍼런스를 통해 item의 실제 값을 마음대로 바꿀 수 있습니다.
의도하지 않은 설계였을 경우 값이 변경될 수 있기에 위험해 집니다.
void Print( const FItem& item )
함수 인자로 레퍼런스를 사용할 때, 원본 변수의 변경을 원치 않는다면
const 키워드를 사용하면 변경을 막을 수 있습니다.
추가로 item을 함수의 인자로 넘기는 것보다
item의 참조 변수인 refItem을 이용하는게 속도가 훨씬 빠릅니다.
(값 복사하지 않고 참조하기 때문)
✅ 예제3-2 - 함수 인자로 일반 변수의 레퍼런스 사용
void Print( int& item )
함수의 인자로 일반 값 타입 변수의 레퍼런스로 넘기게 될 경우
오히려 더 느릴 수 있습니다.
✅ 예제4 - 코드 정리에 사용
struct FA
{
int a;
int b;
__int64 big;
};
struct FB
{
FA structA;
int b;
};
int main()
{
FB test;
// 이런식으로 코드를 깔끔하게 사용할 수 있다.
__int64& big = test.structA.big;
}
✅ 예제5 - 함수 반환형이 참조자
int Add( int& num )
{
++num;
return num;
}
int main()
{
int num = 1;
int num2 = Add( num );
--num;
std::cout << num << std::endl;
std::cout << num2 << std::endl;
return 0;
}
num2는 Add함수에서 num의 증가된 값을 "대입" 받습니다
num과 num2는 같은 주소를 가지고 있지 않습니다.
int& Add( int& num )
{
++num;
return num;
}
int main()
{
int num = 1;
int num2 = Add( num );
int& num3 = Add( num );
int num4 = num3;
++num;
std::cout << num << std::endl;
std::cout << num2 << std::endl;
std::cout << num3 << std::endl;
std::cout << num4 << std::endl;
return 0;
}
- num은 add를통해 2번 증가, 그후 전치 연산을 통해서 한번더 증가 되었기 때문에 4
- num2는 add(num) 호출 후 증가된 num의 값(2)을 "대입" 받아서 2
- num3는 add(num) 호출 후 증가된 num의 값(3)이 된 후 num을 참조하게 되어 num의 값인 4
- num4는 num3이 참조하는 num의 값을 대입 받아서 3
‼️함수에서 참조자 반환시 주의하기 - 댕글링 레퍼런스
int& GetNum()
{
int num = 0;
return num;
}
int main()
{
int& refNum = GetNum();
refNum = 2;
std::cout << refNum << std::endl;
return 0;
}
GetNum() 함수는 지역 변수를 참조하여 반환하고 있습니다.
num 변수는 GetNum() 함수가 끝나면서 메모리에서 소멸하게 되는데,
refNum은 소멸된 메모리를 참조하게 됩니다.
num을 반환할 때 경고가 뜨게 됩니다.
해제된 메모리를 참조하는 참조자를 댕글링 레퍼런스 (Dangling Reference) 라고 합니다.
함수에서 참조자를 반환할 때는 지역변수를 반환하여 댕글링 레퍼런스가 발생하지 않았는지
각별히 주의해야 합니다.
하지만 컴파일러에 따라 제 경우 처럼 출력이 되는 경우도 있습니다.
참고 :