## 목차
1. References
#1 References
1 - 1) 참조자란
- 변수의 별명
- 참조자는 새로운 변수가 아니다.
- 변수가 아니라는 의미는 메모리에 새로운 공간을 차지하지 않는다는 의미이다.
- 포인터 변수는 그 자체가 주소값을 저장하는 하나의 변수였지만, 참조자는 다르다.
- 선언과 동시에 초기화 되어야 한다.(null : x)
- 한 번 초기화되면, 다른 변수의 참조자가 될 수 없다.
- const인 pointer이면서, 사용 시 자동으로 역참조를 수행하는 개념이다.
- 포인터의 간단하고 편리한 버전으로 기억
- C++에서 함수의 매개변수로 자주 사용된다.
- int&, float& 등 변수 정의시에 &를 붙인 타입을사용
- 포인터와 마찬가지로, 동일한 타입에 대해서만 참조자 생성 가능
- 참조자를 사용할 때는 마치 a인 것처럼 그냥 사용(*, &를 붙이지 않음)
예제1)
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& b = a;
b = 20;
cout << a << endl;
int& c; // Error! 무엇의 별명인지 만들 때 명시하여 초기화 해야 함
}
예제2)
#include <iostream>
using namespace std;
void Increment(int val) {
val++;
}
void IncrementByReference(int& val) {
val++;
}
int main() {
int a = 5;
Increment(a);
cout << a << endl;
IncrementByReference(a);
cout << a << endl;
return 0;
}
1. int& val 자신은 지역 변수이지만, a의 참조자기 때문에 포인터처럼 호출 스택 밖의 변수의 값을 바꿀 수 있다.(참조에 의한 호출 방식)
2. 반면에 Increment 함수 내의 지역변수 int val은 단지 a의 값을 복사해온 지역변수이기 때문에, a에 영햐을 미치지 못하고 호출이 끝나면 사라진다.(값에 의한 호출 방식)
예제3)
#include <iostream>
using namespace std;
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);
PrintRef(a);
PrintConstRef(a);
PrintAddress(&a);
}
- Copy가 필요한 경우가 아니라면 , const &를 쓰는 것이 기본이다.
- const& rvalue 전달 가능
- 오른쪽 코드는 모두 a의 값인 5를 그대로 출력한다.
- 4개의 Print 함수의 장/단점
PrintConstRef(const int& val):
장점:
상수 또는 임시 값에 대한 참조를 허용하므로, 함수 호출 시 복사본이 생성되지 않습니다. 따라서 메모리 사용량을 줄일 수 있습니다.
변수의 값을 변경하지 않으므로 함수가 값을 수정하는 것을 방지합니다.
단점:
참조로 전달되기 때문에 함수 내에서 변수를 수정할 수 없습니다. 따라서 읽기 전용인 상황에만 사용할 수 있습니다.
PrintAddress(int* valPtr):
장점:
포인터를 사용하여 변수의 메모리 주소를 전달하기 때문에 함수 내에서 변수를 직접적으로 수정할 수 있습니다.
단점:
포인터를 전달하기 때문에 함수 호출 시 포인터의 복사본이 생성되며, 이는 메모리 사용량을 증가시킬 수 있습니다.
포인터를 사용하므로 호출할 때 주소를 전달해야 하므로 호출자 코드가 더 복잡할 수 있습니다.
PrintRef(int& val):
장점:
참조를 사용하여 변수의 복사본을 생성하지 않으므로 메모리 사용량을 줄일 수 있습니다.
함수 내에서 변수를 직접적으로 수정할 수 있습니다.
단점:
변수의 값을 변경할 수 있으므로 호출자가 원하지 않는 변수의 수정이 발생할 수 있습니다.
PrintVal(int val):
장점:
변수를 값으로 전달하기 때문에 호출자 코드가 더 간단할 수 있습니다.
단점:
함수 호출 시 변수의 복사본이 생성되므로 메모리 사용량이 증가할 수 있습니다.
함수 내에서 변수를 수정해도 호출자에게 영향을 주지 않습니다.
예제4)
#include <iostream>
using namespace std;
int main() {
int a = 2;
int& b = a;
int* c = &b;
cout << (c == &a) << endl; // true이기에 1이 출력
return 0;
}
- 참조자가 새로운 변수가 아니라는 의미
- 메모리 디버깅을 통해 a의 주소와 b의 주소를 살펴보면 동일한 것을 알 수 있다. 즉, b가 포인터와는 달리 메모리 공간을 차지하는 새로운 변수는 아니라는 의미
- 컴파일 시에 참조자는 가리키는 원본의 메모리 주소로 대체되는 방식이다.
1 - 2) l-value & r-value
l-value
- 이름을 가지며, 주소를 갖는 값
- const가 아니라면 수정이 가능한 값
int x = 100; // x is l-value
string name = "Wooki" // name is l-value
r-value
- 주소를 갖지 않고 대입의 대상이 될 수 없다.
- l-value가 아닌 것들
- 대입식의 오른쪽에 위치
- 리터럴 등
int x = 100; // 100 is r-value
string name = "Wooki"; // "Wooki" is r-value
int maxNum = max(20, 30); // max(20,30) is r-value
기본적으로는 l-value인 경우에만 참조자를 만들 수 있으나, const reference인 경우와 r-value reference에 대한 문법 또한 존재한다.
int x = 100;
int& ref1 = x;
ref1 = 200;
int& ref2 = 100; //Error! 100 is not l-value
const int& ref3 = 100; // allowed
int&& ref4 = 100; // allowed, r-value reference
1 - 3) 포인터 vs 참조자
간단 정리
- *의 사용
- 변수를 정의할 때 붙는다 -> 포인터의 정의(포인터 변수의 생성)
int* a
- 변수를 사용할 때 붙는다 -> 포인터의 역참조
*a
- &의 사용
- 변수를 정의할 때 붙는다 -> 참조자의 정의(참조자 생성)
int& b
- 변수를 사용할 때 붙는다 -> 변수의 주소값 반환
&a
pass-by-value
- 함수가 길제 매개변수(원본)를 수정하지 않는 경우에 사용한다.
- int, char, double과 같은 크기가 작은 기본 자료형의 전달
- 기본 자료형이 아닌 자료형 -> string, vector, clss
- 복사의 비용이 높기 때문이다.
void func(int a)
pass-by-address(포인터 사용)
- 함수가 실제 매개변수(원본)를 수정해야 하는 경우
- 즉, 지역 범위 밖의 메모리에 저장된 값에 접근해야 하는 경우
- 복사의 오버헤드가 클 때
- 포인터가 nullptr이 되어도 상관 없을 때
- 참조자는 null이 될 수 없다.
void func(int* a)
const의 포인터를 사용한 pass-by-address
- 함수가 실제 매개변수를 수정하지 않는 경우
- 복사의 오버헤드가 클 때
- 포인터가 nullptr이 되어도 상관 없을 때
- 참조자는 null이 될 수 없다.
void func(const int* a)
const의 const인 포인터를 사용한 pass-by-address
- 함수가 실제 매개변수를 수정하지 않는 경우
- 복사의 오버헤드가 클 때
- 포인터가 nullptr이 되어도 상관 없을 때
- 참조자는 null이 될 수 없다.
- 포인터가 바뀌지 않아야 할 때
void func(const int* const a)
pass-by-reference(참조자 사용)
- 함수가 실제 매개변수를 수정하는 경우
- 복사의 오버헤드가 클 때
- 매개변수가 null이 되지 않는 것이 보장될 때
void func(int& a)
const 참조자를 사용한 pass-by-const-reference
- 함수가 실제 매개변수를 수정하지 않는 경우
- 복사의 오버헤드가 클 때
- 매개변수가 null이 되지 않는 것이 보장될 때
void func(const int& a)
※본 블로그는 학습을 하며 제가 이해한 내용을 바탕으로 작성되어 실제 정의와 다를 수 있습니다.
참고문헌
GitHub - diskhkme/cpp_lecture_material: C++ 프로그래밍 강의 자료
https://www.aladin.co.kr/shop/wproduct.aspx?ISBN=K842939734&start=pnaver_02
'ZAION > C++' 카테고리의 다른 글
[C++]Lec04-01. Pointer & Reference (0) | 2024.04.26 |
---|---|
[C++]Lec03. Function (0) | 2024.04.25 |
[C++]Lec02. Debugging (0) | 2024.04.24 |
[C++]Lec02. Basic Syntax (0) | 2024.04.23 |
[C++]Lec01. Variable and Constant (1) | 2024.04.23 |