참조자란?
- 변수가 아니다. 👉 변수는 메모리 공간을 차지한다.
- 하지만 참조자는 메모리 공간을 차지하지 않는 '변수의 별명' 이다. (엄청난 장점)
- 선언과 동시에 반드시 초기화 되어야 한다. 👉 참조자는 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;
}
위 코드의 출력 결과가 무엇일지 생각해보면 된다.
'C++' 카테고리의 다른 글
[C++] 객체지향 프로그래밍 - 클래스와 객체 (2) (0) | 2024.10.26 |
---|---|
[C++] 객체지향 프로그래밍 - 클래스와 객체 (1) | 2024.10.20 |
[C++] 포인터를 함수에 input, return (1) | 2024.10.13 |