본문 바로가기

C++

[C++] 참조자 Reference

 

참조자란?

  • 변수가 아니다. 👉 변수는 메모리 공간을 차지한다.
  • 하지만 참조자는 메모리 공간을 차지하지 않는 '변수의 별명' 이다. (엄청난 장점)
  • 선언과 동시에 반드시 초기화 되어야 한다. 👉 참조자는 Null 값을 가질 수 없다. 포인터의 경우에는 int* a; 처럼 초기화 하지 않은 선언이 가능했지만, 참조자의 경우에는 int& a=b; 처럼 반드시 변수를 입력해주어야 한다.
  • Const pointer이면서, 사용시 자동으로 역참조를 수행함 👉 포인터의 편리한 버전이라고 생각하면 편하다.
  • 함수의 매개변수로 자주 사용

 

#include <iostream>

using namespace std;

int main()
{
	int a = 10;
	int& b = a;

	b = 20;
	cout << a << endl;
	
	//int& c; -> Error
}

 

  • 위 코드의 결과는 20
  • 포인터에서도 그랬던 것처럼, 동일한 타입에 대해서만 참조자 생성이 가능 👉 int&만이 int 변수를 참조할 수 있다.
  • 위 코드에서 int& b = a;는 앞으로 b를 마치 a인 것처럼 사용하겠다는 의미이다. 👉 a와 b는 서로 같은 메모리 주소를 가리킨다.

 

 

 

void Increment(int val)
{
	val++;
}

void IncrementByReference(int& val)
{
	val++;
}

int main()
{
	int a = 5;
	Increment(a);	
	cout << a << endl;		// 결과 5

	IncrementByReference(a);
	cout << a << endl;		// 결과 6

	return 0;
}

 

  • Increment() 함수로는 a의 값을 바꿀 수 없다. 👉 Increment()의 입력변수 val은 a 값을 복사해 온 지역변수이기 때문에, 해당 함수가 끝나면 val은 사라진다.
  • 하지만 IncrementByReference()는 a 값을 바꿀 수 있다. 👉 int &val 또한 해당 함수의 지역변수인 것은 맞지만, 단순히 값을 복사해온 것이 아닌, a와 동일한 메모리 주소를 가리키고 있는 = a와 동일하게 작동하는 변수이기 때문.

 

 

 

 

void PrintConstRef(const int& val)
{
	cout << val << endl;
}
void PrintAddress(int* valPtr)
{
	cout << *valPtr << endl;
}
void PrintRef(int& val)
{
	cout << val << endl;
}
void PrintVal(int val)
{
	cout << val << endl;
}
int main()
{
	int a = 5;
	PrintVal(a);		// 5
	PrintRef(a);		// 5
	PrintConstRef(a);	// 5
	PrintAddress(&a);	// 5

	return 0;
}

 

  • 네 함수의 결과는 모두 5로 같다. 그렇다면 어떤 방식으로 출력하는 것이 가장 바람직한가?
  • PrintConstRef(const int& val) 👉 입력인자로 받은 val을 함수 내에서 변경할 수 없음. 그렇기 때문에, 입력변수를 함수 내에서 변경하지 않을 예정일 때 아주 유용하다. 참조자를 사용하기 때문에 메모리를 잡아먹지도 않고, const를 명시함으로써 입력변수를 실수로 변경하는 가능성도 제거한다. 이러한 경우에 대하여 "더 효율적이기 때문에" 사용한다.
  • 따라서 Copy가 필요한 경우가 아니라면 const &를 사용하여 copy 대신 변수의 메모리 주소만 가져오도록 한다.

 

 

 

 

int main()
{
	int a = 2;
	int& b = a;
	int* c = &b;

	cout << (c == &a) << endl;	// 1

	return 0;
}

 

  • b는 a를 참조하며, c는 b의 주소를 담고있다.
  • 그렇다면 c는 a의 주소를 담고있는 것이다.

 

 

 

l-value와 r-value

  • l-value 👉 이름과 주소를 갖는 값. const가 아닌 경우에 수정이 가능한 값이다.
    • int x = 100; 이라는 문장이 있을 때, x는 l-value이다.
    • string name = "hi"; 라는 문장이 있을 때, name은 l-value이다.

 

  • r-value 👉 주소를 갖지 않으며, 대입의 대상이 될 수 없는 값. 대입식의 오른쪽에 위치하는 값들
    • int x = 100; 이라는 문장이 있을 때, 100은 r-value이다.
    • string name = "hi"; 라는 문장이 있을 때, hi는 r-value이다.

 

 

 

 

 

Pointers vs References

  • *의 사용
    • 변수를 정의할 때 붙는다 👉 포인터 변수의 생성   int* a
    • 변수를 사용할 때 붙는다 👉 포인터의 역참조        *a
    • 예를 들어 int* a = &b;를 통해 포인터 변수 a에 b의 주소를 넣어놨다고 하자. 이때 a는 &b를 담고있지만, *a는 역참조로써 b에 접근한다. 따라서 *a = 10; 등의 작업이 가능하다.

 

  • &의 사용
    • 변수를 정의할 때 붙는다 👉 참조자의 생성          int& a
    • 변수를 사용할 때 붙는다 👉 변수의 주소값 반환   &a
    • int& a = b;를 수행하면 a와 b는 같은 것으로 취급할 수 있다. 따라서 &a == &b가 되고,  a = 10;을 수행하면 b 역시 10이 된다.

 

 

 

#include <iostream>

using namespace std;

int main()
{
	int x = 100;
	int& ref1 = x;
	int* pointer1 = &x;

	cout << ref1 << endl;
	cout << &ref1 << endl;
	cout << pointer1 << endl;
	cout << *pointer1 << endl;
}

 

위 코드의 출력 결과가 무엇일지 생각해보면 된다.