✅ 주제
- C++에서 함수 인자로 구조체 또는 객체를 전달할 때 사용하는 세 가지 방식(값 복사, 포인터, 참조)을 비교하고, 각각의 장단점, 메모리 영향, 안정성, const 참조의 의미, OUT 매크로의 관용적 표현까지 심층적으로 이해하는 실습 중심 강의
이 강의는 함수 호출 시 어떤 방식으로 데이터를 넘기는 것이 효율적인지 판단하고, 참조와 포인터의 내부 구조를 이해하며 안정성과 성능 사이의 균형을 잡는 법을 학습합니다.
📘 개념
1. 값 전달 방식 (Call by Value)
- 데이터 전체를 복사해서 함수에 전달
- 함수 내부에서 값이 변경되더라도 원본에 영향 없음
- 구조체 크기가 크면 복사 비용이 커짐 → 비효율적
2. 포인터 전달 방식 (Call by Pointer)
- 주소를 전달하여 함수 내부에서 원본 데이터를 직접 조작
- 복사 비용 없이 성능에 유리
- nullptr 체크 필요 – 체크하지 않으면 프로그램 크래시 가능
3. 참조 전달 방식 (Call by Reference)
- 복사하지 않고 원본을 참조하는 방식
- 문법적으로 일반 변수처럼 사용
- null 개념이 없기 때문에 안정성↑
- 내부적으로는 포인터처럼 작동하지만, 문법은 간결하고 직관적
📗 용어 정리
| 용어 | 설명 |
|---|
| 값 전달 | 데이터를 복사해서 함수에 전달 |
| 포인터 | 메모리 주소를 저장하는 변수 (* 연산자 사용) |
| 참조 | 기존 변수의 별칭(alias) – 복사 없이 원본 접근 |
| nullptr | 어떤 것도 가리키지 않는 포인터의 상태 |
| const 참조 | 읽기 전용 참조 – 수정 불가 |
| OUT 매크로 | 함수 인자가 출력(변경) 목적인 것을 명시하는 관용적 매크로 |
🧩 코드 분석
🔹 구조체 선언 및 초기화
struct StatInfo {
int hp;
int attack;
int defence;
};
StatInfo player = { 100, 10, 1 };
player.hp = 100;, player.attack = 10;, player.defence = 1;의 축약 형태
- 게임 캐릭터의 상태 정보를 담는 구조체
🔹 1. 값 복사 방식
void PrintByCopy(StatInfo player) {
cout << "-------------------" << endl;
cout << "HP : " << player.hp << endl;
cout << "ATT : " << player.attack << endl;
cout << "DEF : " << player.defence << endl;
cout << "-------------------" << endl;
}
StatInfo player는 원본이 아닌 복사본
- 함수 안에서 player를 변경해도 main의 player에는 영향 없음
- 구조체 크기가 크면 복사 비용 발생
🔹 2. 포인터 전달 방식
void PrintByPointer(StatInfo* player) {
cout << "-------------------" << endl;
cout << "HP : " << player->hp << endl;
cout << "ATT : " << player->attack << endl;
cout << "DEF : " << player->defence << endl;
cout << "-------------------" << endl;
}
&player를 통해 주소 전달
-> 연산자는 포인터가 가리키는 구조체의 멤버 접근을 의미 ((*player).hp와 동일)
- 원본을 수정할 수 있는 방식
- 주의:
nullptr 전달 시 접근하면 크래시 발생 → 체크 필요
🔹 3. 참조 전달 방식
void PrintByRef(StatInfo& player) {
cout << "-------------------" << endl;
cout << "HP : " << player.hp << endl;
cout << "ATT : " << player.attack << endl;
cout << "DEF : " << player.defence << endl;
cout << "-------------------" << endl;
}
& 참조 연산자를 통해 복사 없이 원본 전달
- 함수 안에서는 일반 변수처럼 사용 (
.으로 접근)
- 내부적으로는 포인터와 유사하지만 문법이 직관적
- null이 될 수 없기 때문에 안정성 뛰어남
🔹 참조와 포인터의 차이
| 항목 | 포인터 | 참조 |
|---|
| NULL 가능 여부 | 가능 (nullptr) | 불가능 (초기화 필수) |
| 문법 | *, -> 필요 | 일반 변수처럼 사용 가능 |
| 안정성 | 낮음 (null 체크 필요) | 높음 |
| 수정 가능 여부 | 가능 | 가능 |
| 초기화 여부 | 나중에 가능 | 선언과 동시에만 가능 |
🔹 const 참조
void PrintInfo(const StatInfo& player) {
cout << player.hp << endl;
}
- 읽기 전용으로 데이터를 전달
- 복사 비용 없이 안전하게 사용
- 읽기 전용 출력 함수 등에 적합
🔹 OUT 매크로
#define OUT
void SetStat(OUT StatInfo& player) {
player.hp = 50;
}
OUT은 컴파일 시 사라지는 주석용 매크로
- 함수가 출력용 인자를 수정한다는 의도를 코드로 명시
- 협업 시 코드 가독성 향상
🎯 핵심 요약
| 전달 방식 | 복사 여부 | 원본 수정 가능 | 안정성 | 메모리 효율 |
|---|
| 값 복사 | O | X | O | X |
| 포인터 | X | O | X | O |
| 참조 | X | O | O | O |
| const 참조 | X | X | O | O |
| OUT 참조 | X | O | O | O |