참조란? 쉽게 말해서 대상에게 또 다른 이름(별칭)을 붙여주는 것
int num = 1;
은 메모리 특정 영역에 int 만큼의 크기를 할당하고, 1이라는 데이터를 복사하는 것이다. 이 할당된 메모리의 이름은 num이 되고 이것을 변수라고 한다.
int& ref = num;
은 num을 참조하는 참조자인 ref를 선언하는 것이다. 이렇게 선언하고 나서는 이 변수는 num말고 ref라는 이름도 갖게 되는 것이다. 즉, 하나의 메모리 공간에 이름이 2개가 생기고 이 둘의 주소값은 같다. (2개이상의 참조자도 만들 수 있다)
- 선언 시 &연산자를 붙이고 선언과 동시에 참조할 대상을 지정해서 초기화를 해줄 필요가 있다.
- 한 번 누군가를 참조하도록 선언하면 참조의 대상을 바꿀 수는 없다.
- 일반 참조자는 null 참조가 불가능 (null은 상수 값 0을 define한 것이기 때문) -> 포인터와의 차이점
- 비상수 참조자는 const를 사용하면 상수를 참조할 수 있다.
- 또한 const를 사용하면 리터럴 상수도 참조할 수 있다.
(리터럴 상수란 임시적인 값으로써 메모리에 이름도 없이 존재 했다가 다음 행에서 소멸하는 것을 의미)
=> const int& ref3 = 100; (리터럴 상수) : 임시 변수가 생성되어 참조가 가능
const 참조를 사용해서 굳이 상수를 참조하는 이유?
- Add 함수의 매개변수를 const참조자로 받을 때
int Add(const int& num1, const int& num2) // const 참조자형 매개변수
{
return num1 + num2;
}
int main(void)
{
std::cout << Add(1, 2) << std::endl; // 상수를 곧바로 호출
return 0;
}
2. const 참조가 아닌 그냥 참조로 매개변수를 받을 때는..
2. int Add(int& num1, int& num2) // non-const 참조자형 매개변수
{
return num1 + num2;
}
int main(void)
{
int num1 = 1;
int num2 = 2;
std::cout << Add(num1, num2) << std::endl; // 변수를 전달
return 0;
}
➡️ 이처럼 변수를 번거롭게 선언해서 변수를 전달해야 함
참조자를 이용한 함수 호출의 장점
void PrintNum(const int arg) // 매개변수에 const를 붙여 의도치 않은 변형을 방지
{
std::cout << arg << std::endl;
}
int main(void)
{
int num = 1;
PrintNum(num); // const int arg = num; -> num의 크기만큼 메모리 재할당이 일어남
return 0;
}
---------------------------------------------------------------------------
void PrintNum(const int& arg) // 매개변수에 const를 붙여 의도치 않은 변형을 방지
{ // 매개변수를 참조자로 변경
std::cout << arg << std::endl;
}
int main(void)
{
int num = 1;
PrintNum(num); // const int& arg = num;
return 0;
}
‼️함수의 매개변수를 값이 아닌 참조자로 받아서 num의 동일한 메모리에 arg라는 또 다른 이름만 부여하는 것이기 때문에 메모리의 재할당이 일어나지 않아서 효율적‼️
🔥참조자의 근본적인 문제점🔥
int num1 = 1;
int num2 = 2;
DoSomething(num1, num2);
위와 같은 경우 DoSoemthing이 끝나고 num1, num2의 값이 어떻게 될 지 모르기 때문에 함수 호출자 입장에서 이 함수가 매개변수로 일반 변수를 받는지, 참조자를 받는지 알 수 없다. (해결방안 아직 x)
따라서 ! 함수 작성자는 함수의 매개변수가 참조자이고, 이 매개변수가 변형될 일이 없다면 매개변수를 const 참조자로 작성해야 하며, 함수 호출자 또한 함수가 매개변수를 어떤 데이터 형으로 받는지 확인해야 한다.
❇️ 반환형이 참조자인 함수
int& AddNum(int& arg) // 참조자 반환
{
arg++;
return arg;
}
int main(void)
{
int num1 = 1;
int num2 = AddNum(num1); // num1과 num2는 별개의 변수가 됨
num1++;
std::cout << num1 << std::endl; // 3
std::cout << num2 << std::endl; // 2
int& num3 = AddNum(num1); // 참조자로 받았기 때문에 num1과 num3은 같음
num1++;
std::cout << num1 << std::endl; // 5
std::cout << num3 << std::endl; // 5
return 0;
}
- 일반 변수로 참조자를 반환 받아도 가능. num2에 arg값만 복사가 됨.
- 참조자로 참조자를 반환 받을 때, 즉!! AddNum(num1)을 했을 때 num1의 또 다른 이름인 arg가 붙었었는데 이번에도 또 다른 이름인 num3이 붙은 것이라고 생각하면 된다!!! => int& num3 = num1; 이 되고 따라서 num1, num3은 같은 메모리 위치에 있는 것
❌반환형이 참조자인 함수에서 발생하는 오류❌
int& GetNum()
{
int ret = 1;
return ret; // Compile Warning
}
int main(void)
{
int& num = GetNum();
num = 2; // Runtime Error
return 0;
}
=> GetNum 함수의 반환형이 참조자인데 지역 변수를 반환하고 있음. 이렇게 되면 num은 ret을 참조하고 싶지만 ret은 지역 변수이기 때문에 GetNum 함수가 끝나면서 메모리에서 소멸하게 되고 소멸된 메모리를 참조하려는 오류를 범하게 된다.
(해제된 메모리를 참조하는 참조자를 댕글링 레퍼런스(Dangling Reference)라고 한다.
🍏const위치와 사용 방법
- const 변수는 반드시 초기화를 해야하며 초기화가 되지 않으면 컴파일 에러가 발생한다. 그래서 class 멤버 변수를 const로 선언 시에는 반드시 초기화 리스트를 사용해야 한다.
=> 초기화리스트를 사용하지 않고 함수 내부에서 const 변수를 초기화 하려고 하면 const 변수를 선언한 후 1로 수정한다는 의미가 되는 것이기 때문에 컴파일 에러가 발생한다. (c++11 부터는 내부에서 선언 초기화가 가능하지만 일반적으로 초기화리스트를 사용)
1) const 포인터 변수
1-1) const 위치가 맨 앞, 포인터 변수가 가리키는 값에 대하여 상수화를 시킴
int num = 1;
const int* ptr = # // *ptr을 상수화 시킴
*ptr = 2; // 컴파일 에러
num =2; // pass
➡️ *ptr = 2; 는 ptr이 const 변수이기 때문에 컴파일 에러가 발생함. 즉, 포인터 변수가 가리키는 num자체가 상수화가 되는 것은 아니다.
1-2) const 위치가 Type과 변수 사이에 위치하고 포인터 변수 자체를 상수화 시키는 경우
int num1 = 1;
int num2 = 2;
int* const ptr = &num1; // ptr을 상수화
ptr = &num2; // 컴파일에러
➡️포인터 변수란 대상의 주소 값을 저장하는 변수. 즉, 위의 ptr은 자기 자신을 상수화 시키는 것이기 때문에 num2의 주소값으로 변경하려고 하면 컴파일에러가 발생
‼️사실 C++에는 참조자가 있기 때문에 포인터를 상수화 시키려면 const int* const ptr = # 과 같이 번거롭게 작성해야함. 참조자는 애초에 참조하는 대상을 변경할 수 없는 특징이 있기 때문에 const int& ref = num; 의 형태로 사용된다.
2) const 멤버 함수
const 멤버 함수는 class 멤버 함수만 const로 상수화 시킬 수 있고 비멤버함수는 선언이 불가능함
const의 위치는 함수 선언문 맨 뒤에 위치 : 해당 멤버 함수 내에서는 모든 멤버 변수를 상수화 시킨다는 의미 !
Ex )
int GetString(void) const; // Compile Error
class Foo
{
int num = 1;
int GetNum(void) const
{
int a = 1;
a++; // 지역 변수는 가능
num++; // Compile Error , 멤버 변수 변경 불가능
return num;
}
};
작년 C++ 공부할 때 어려웠던 참조 개념에 대해서 정리한 것들