하아찡
레퍼런스(참조자) 란? 본문
포인터를 배우면서 같이 배우게 되는 레퍼런스(참조자)는 참 알쏭 달쏭한 친구죠.
결과부터 말하자면
제가생각하는 레퍼런스를 가장쉽게 설명하는방법은 "친구의 별명" 이라고 생각합니다.
자 여기서 왜 친구의 별명이라고 설명을 했는지를 알고 넘어가야하죠.
만약 우리가 A라는 친구를 "똥쟁이" 라고 별명을 지었어요.
그러면 우리는 "똥쟁이"를 부르면 A를 부르는걸 알고있죠? 이렇게 간단하게 생각하면 됩니다.
하지만 우리가 공부를 하는데 있어서 간단하게만 알고 넘어갈수도 있지만, 좀 더 알고싶은데? 라고 생각하신다면 더 읽어보셔도 되겠습니다.
일단 기본적으로 "포인터는 메모리상에 남게되고, 참조자는 메모리에 남을수도 있다." 라는 말이 있습니다.
그러면 구체적으로 뭐가 다른지를 다시 확인을 하고 넘어가야겠죠.
포인터는 주소를 저장하는 변수입니다.
여기서 중요한 말이 무엇이냐, "주소를 저장" 이말이 중요합니다. 저장을 한다? 누구에게 저장을 하냐 -> 컴퓨터에게 저장하라고 시킵니다. 그러면 컴퓨터는 저장할 메모리를 빌려주게 됩니다. 그로인해 포인터는 메모리를 빌려야만 사용 할 수 있고, 빌렸기 때문에 null값을 가지고 있을수도 있는겁니다.
그러면 우리 참조자를 봅시다.
참조자는 위에서 설명했듯이 별명입니다.
"누구의 별명"입니다. 근데 이건 누구의 별명인지 필요하죠? 그래서 참조자는 만들때보면 항상 선언과 동시의 누구를 이러한 별명으로 지을 꺼냐 라고 확실하게 알려줘야 컴파일 과정에서 에러가 생성되지 않습니다.
int a = 50;
int& nickname = a;
참조자를 사용할땐 위와같은 코드로 "a"라는 변수를 nickname으로 사용하겠다 라고 하는겁니다.
참 간단하죠?
근데 이쯤오면 생각이 듭니다. 어떤생각? "이거 어따씀?" 아주 강하게 드는 생각입니다.
그래 알겠어 별명 좋아 근데 우리 이걸 왜 머리아프게 더 알아야해? 그냥 포인터를 쓰면 포인터를 쓰면 되는거아니야?
그래서 우리 레퍼런스를 미워하지말아요 그래도 사용하는 이유가 있으닌까요. 하나씩 알아봅시다.
우리가 포인터를 공부함으로써 " * " 별이라는 친구를 붙여서 주소값에 있는 값을 변경하거나 값을 가져오는 행동을 했습니다. 이런 행동이 불편할때 헷갈림을 방지하기위해 우리 레퍼런스를 사용 할 수 있습니다!
자 코드로 보시죵
#include <iostream>
using namespace std;
int main()
{
int pointer_base = 50;
int* a = &pointer_base;
(*a)++;
cout << pointer_base << endl;
int& nickname = *a;
nickname++;
cout << pointer_base << endl;
}
위 코드를 실행시켜보면 아래와 같이 결과가 나오게 됩니다.
51
52
위에 코드를 보면 pointer_base라는 변수를 a라는 포인터가 주소값을 받게되어 값 사용 할 수 있게 됐습니다.
하지만 우리가 값을 변경하기위해 " * " 별 친구를 사용해서 값을 변경해줘야하는 불편한 과정이 생기고 처음에는 생소합니다. 그래서 해당 a포인터 주소를 nickname이라는 레퍼런스에게 넘겨주면 레퍼런스는 우리가 일반적인 변수를 사용하듯이 사용이 가능하게 됩니다.
하지만 뭐 다 생각이 있게 만든거겠죠...?
여기까지만 보고 가지마세요 온김에 더보고 가시죠.
이번에 볼내용은 "이정도는 레퍼런스 사용할만하네? 참조자 이럴때 쓰면 좋겠네" 라는 생각이 드실껍니다.
저희가 함수를 만들때 매개변수를 사용해서 함수 내부에서 사용하는 변수가 있습니다.
해당 매개변수는 외부에 존재하는 상수값을 전달해주던가 변수안에 있는 값을 전달을 해주는 방식으로 사용을 하게되죠.
하지만 매개변수를 만들때 레퍼런스를 붙여서 함수를 만들게되면 어떠한 일이 발생이 될까요?
위에서 말했듯이 레퍼런스는 별명입니다. 그리고 우리가 설명을 들을때 항상 메모리를 사.용.할.수.도 있습니다.
저 사용할수도 있습니다. 라는 말은 다시 생각해보면 메모리를 사용을 안하는 경우가 더 많다고 볼 수 있습니다.
자그러면 메모리를 사용을 안하면 좋은점 이 무엇이 있는지를 생각해봅시다.
직관적으로 봤을때 컴퓨터자원을 덜 먹습니다. 이만한 장점이 없죠. 하지만 우리 메모리부자인 현재 걱정없이 사용하죠.
그래도 우리는 공부를 하는 입장에서 불필요한경우에는 그러지 않아도 되지않을까요?
자 이런말이 왜 나왔냐. 우리가 함수를 호출할때 매개변수를 전달해주면 외부에도 변수가 존재하고, 내부함수에서도 매개변수가 변수로 존재하게됩니다. 그로인해 우리는 또 하나의 변수가 생성이 된다고 생각하면됩니다.
근데 만약 나는 또다른 변수가 생성이 되지않았으면 좋겠어 할때는 매개변수를 만들어줄때 참조자를 붙여서 생성해주면 됩니다.
그러면 또다른 메모리르 생성하지않고 별명을 통해 원래 주소를 알고있으니 함수 내부에서 원래주소의 값을 사용 할 수 있게됩니다.
말이 어렵지 코드로 봅시다.
#include <iostream>
using namespace std;
void printValue(int& ref) {
cout << ref << endl;
ref = 80;
}
int main()
{
int pointer_base = 50;
printValue(pointer_base);
cout << pointer_base << endl;
}
위 코드를 실행시켜보면
50
80
위와 같은 결과가 나오게됩니다.
함수 내부에서 ref값을 변경해줬지만 외부에서 전달해준 변수값도 변경이 된 모습을 확인하실 수 있습니다.
이렇게 내부에서 처리한 결과가 외부에서도 변경이 되는건 해당 메모리 주소를 알아야지만 가능한 경우이기도 합니다.
그래서 매개변수를 레퍼런스로 사용해서 만들었을 경우 함수 내부에서 따로 변수를 생성하지않고 해당 주소를 사용하기때문에 복사가 발생하지 않게됩니다.
다른 코드도 보시죠
#include <iostream>
using namespace std;
void printValue(const int& ref) {
cout << ref << endl;
ref = 80;
}
int main()
{
int pointer_base = 50;
printValue(pointer_base);
cout << pointer_base << endl;
}
위 코드는 매개변수에 const를 붙여서 사용하는 모습입니다.
위코드를 컴파일시켜보면 당연하게도 오류가 발생하게 됩니다. 왜죠? const가 붙으면 우리는 상수취급이 됩니다.
상수를 변경할라고하면 안되겠죠?
그러면 저렇게는 어떨때 쓸까요?
우리가 함수를 호출할때 상수값으로도 호출을 할 경우가 발생할때 사용합니니다.
왜? 매개변수에 레퍼런스를 붙였을때 상수값으로는 호출이 불가능하게 됩니다.
#include <iostream>
using namespace std;
void printValue(const int& ref) {
cout << ref << endl;
}
int main()
{
printValue(50);
}
위와 같이 코드를 작성해두면 상수로도 호출이 가능한 함수로 탄생하게 됩니다.
여기까지 보면 자신이 포인터공부를 좀 하셨다면 의문이 생기는게 있습니다.
포인터도 메모리 주소를 사용하고, 레퍼런스도 메모리 주소를 사용하는데 그러면 포인터 주소를 사용해도 되는거 아닌가?
답부터 말씀드리면 사용하셔도 됩니다.
다만 차이가 있다면
매개변수에 포인터를 붙여서 만드는건 메모리에 포인터 크기만큼 주소를 할당받습니다. 즉 메모리를 사용합니다.
레퍼런스와 다른경우는 이런겁니다.
간단하게 정리하면 "레퍼런스는 변수 원본을 사용하는것이고, 포인터는 수정가능한 원본 주소를 알고있는 변수다" 라고 생각해주시면 됩니다.
그래서 원본주소를 저장하기위한 변수를 생성하기위해 메모리를 사용하게됩니다.
그러면 둘중에 뭘 사용해야하는가?
상황에 따라 다 다릅니다.
전달받은 매개변수가 동적으로 변하지않는다. 그러면 레퍼런스를 사용하셔도 되고, 동적으로 변수가 담고있는 주소가 변해야한다면 포인터 주소를 사용하셔야합니다.
레퍼런스는 별명입니다. 이미 별명을 붙여주면 변경이 불가능하게 됩니다!
그리고 레퍼런스는 Null처리가 불가능함으로 Null처리가 필요하다면 당연하게도 포인터를 사용하셔야 합니다.
그래서 매개변수를 통해 데이터를 많이 전달해줘야 하고, 해당 주소가 변하지 않는 작업이라면
무분별한 복사를 하지않게 레퍼런스를 사용해서 매개변수를 사용하시면 좋습니다.
#include <iostream>
#include <vector>
using namespace std;
void printValue(vector<int>& ref) {
cout << ref.size() << endl;
ref.push_back(5000);
cout << &ref << endl;
}
int main()
{
vector<int> data; // 정수를 저장할 벡터
int max = 10;
for (int i = 1; i <= max; i++) {
data.push_back(i);
}
cout << &data << endl;
printValue(data);
cout << data.size() << endl;
}
레퍼런스를 붙였을경우 함수 진입전 data주소와 ref주소가 동일하게 보여지고
#include <iostream>
#include <vector>
using namespace std;
void printValue(vector<int> ref) {
cout << ref.size() << endl;
ref.push_back(5000);
cout << &ref << endl;
}
int main()
{
vector<int> data; // 정수를 저장할 벡터
int max = 10;
for (int i = 1; i <= max; i++) {
data.push_back(i);
}
cout << &data << endl;
printValue(data);
cout << data.size() << endl;
}
레퍼런스를 붙이지 않았을경우 data주소와 ref주소가 다른걸보니 데이터 복사가 일어났다. 라고 보실 수 있습니다.
공부하면서 적은 내용이기때문에 틀린점이 있을 수 있습니다.
혹시 보시다가 고칠점이 있다면 알려주시면 감사합니다!
'C++ > 기본문법' 카테고리의 다른 글
mutex (1) | 2024.12.26 |
---|---|
접근제한자, 캡슐화 (0) | 2024.12.16 |
오버라이딩, 오버로딩 (1) | 2024.12.15 |
변수란? (0) | 2024.12.02 |