본문 바로가기
ZAION/C++

[C++]Lec04-02. Pointer & Reference

by 우기37 2024. 4. 30.

 

## 목차

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