참조란 무엇인가?
실생활 비유로 이해하기
- 본명: 김철수
- 별명: 철수, 철수야, 수야
- 특징: 어떤 별명으로 불러도 같은 사람을 가리킴
C++의 참조와 비교시
- 원본 변수: 실제 메모리에 저장된 변수
- 참조 변수: 원본 변수의 또 다른 이름 (별명)
- 특징: 참조로 접근하든 원본으로 접근하든 완전히 같은 변수
포인터 vs 참조 비교
특징 포인터 참조
개념 | 메모리 주소를 저장 | 변수의 별명 |
선언 | int* ptr | int& ref |
초기화 | 나중에 가능 | 선언과 동시에 필수 |
재할당 | 다른 변수를 가리킬 수 있음 | 불가능 |
null 값 | 가능 (nullptr) | 불가능 |
메모리 | 별도의 메모리 공간 차지 | 메모리 공간 차지 안함 |
연산 | 포인터 연산 가능 | 불가능 |
참조의 기본 사용법
기본 문법
#include <iostream>
using namespace std;
int main() {
int original = 100;
int& ref = original; // ref는 original의 별명
cout << "=== 초기 상태 ===" << endl;
cout << "original: " << original << endl;
cout << "ref: " << ref << endl;
cout << "original 주소: " << &original << endl;
cout << "ref 주소: " << &ref << endl; // 같은 주소!
// 참조를 통한 값 변경
ref = 200;
cout << "\\n=== ref = 200 후 ===" << endl;
cout << "original: " << original << endl; // 200으로 변경됨
cout << "ref: " << ref << endl;
// 원본을 통한 값 변경
original = 300;
cout << "\\n=== original = 300 후 ===" << endl;
cout << "original: " << original << endl;
cout << "ref: " << ref << endl; // 300으로 변경됨
return 0;
}
참조의 특별한 성질
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
// 참조 생성
int& refA = a;
cout << "refA = " << refA << endl; // 10
// 참조는 다른 변수를 참조할 수 없음
// int& refA = b; // 오류! 이미 선언된 참조
// 새로운 참조 생성
int& refB = b;
cout << "refB = " << refB << endl; // 20
// 참조의 참조도 가능
int& refRefA = refA;
refRefA = 99;
cout << "a = " << a << endl; // 99
return 0;
}
함수에서의 참조 활용
1. 참조로 매개변수 전달
#include <iostream>
using namespace std;
// 값 전달 (Pass by Value)
void incrementValue(int num) {
num++;
cout << "함수 내부 num: " << num << endl;
}
// 참조 전달 (Pass by Reference)
void incrementReference(int& num) {
num++;
cout << "함수 내부 num: " << num << endl;
}
// 포인터 전달 (Pass by Pointer)
void incrementPointer(int* num) {
(*num)++;
cout << "함수 내부 *num: " << *num << endl;
}
int main() {
int value = 10;
cout << "=== 원본 값 ===" << endl;
cout << "value: " << value << endl;
cout << "\\n=== 값 전달 테스트 ===" << endl;
incrementValue(value);
cout << "호출 후 value: " << value << endl; // 변경되지 않음
cout << "\\n=== 참조 전달 테스트 ===" << endl;
incrementReference(value);
cout << "호출 후 value: " << value << endl; // 변경됨
cout << "\\n=== 포인터 전달 테스트 ===" << endl;
incrementPointer(&value);
cout << "호출 후 value: " << value << endl; // 변경됨
return 0;
}
2. 함수 반환값으로 참조 사용
#include <iostream>
using namespace std;
// 전역 변수 (예시용)
int globalVar = 100;
// 참조 반환 함수
int& getGlobalRef() {
return globalVar;
}
// 위험한 참조 반환 (지역 변수)
int& dangerousFunction() {
int localVar = 50;
// return localVar; // 위험! 지역 변수의 참조 반환
return globalVar; // 안전한 대안
}
int main() {
cout << "=== 참조 반환 함수 ===" << endl;
cout << "globalVar: " << globalVar << endl;
// 참조를 반환받아 직접 수정
int& ref = getGlobalRef();
ref = 200;
cout << "ref 수정 후 globalVar: " << globalVar << endl;
// 반환된 참조를 직접 사용
getGlobalRef() = 300;
cout << "직접 수정 후 globalVar: " << globalVar << endl;
return 0;
}
참조와 포인터 변환
참조와 포인터 간의 관계
#include <iostream>
using namespace std;
int main() {
int number = 42;
// 참조 생성
int& ref = number;
// 참조의 주소 = 포인터
int* ptrFromRef = &ref;
// 포인터의 역참조 = 참조
int& refFromPtr = *ptrFromRef;
cout << "number: " << number << endl;
cout << "ref: " << ref << endl;
cout << "*ptrFromRef: " << *ptrFromRef << endl;
cout << "refFromPtr: " << refFromPtr << endl;
// 모두 같은 값 출력
cout << "\\n=== 주소 비교 ===" << endl;
cout << "&number: " << &number << endl;
cout << "&ref: " << &ref << endl;
cout << "ptrFromRef: " << ptrFromRef << endl;
cout << "&refFromPtr: " << &refFromPtr << endl;
return 0;
}
실용적인 참조 활용 예제
1. 배열 원소의 참조
#include <iostream>
using namespace std;
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 배열 원소의 참조 생성
int& firstElement = arr[0];
int& middleElement = arr[2];
cout << "=== 초기 배열 ===" << endl;
for (int i = 0; i < 5; i++) {
cout << "arr[" << i << "] = " << arr[i] << endl;
}
// 참조를 통한 배열 수정
firstElement = 99;
middleElement = 88;
cout << "\\n=== 수정된 배열 ===" << endl;
for (int i = 0; i < 5; i++) {
cout << "arr[" << i << "] = " << arr[i] << endl;
}
return 0;
}
2. 구조체와 참조
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
double gpa;
};
// 구조체를 참조로 받는 함수
void printStudent(const Student& student) {
cout << "이름: " << student.name << endl;
cout << "나이: " << student.age << endl;
cout << "학점: " << student.gpa << endl;
}
// 구조체를 참조로 받아 수정하는 함수
void updateGPA(Student& student, double newGPA) {
student.gpa = newGPA;
cout << student.name << "의 학점을 " << newGPA << "로 업데이트했습니다." << endl;
}
int main() {
Student student1 = {"김철수", 20, 3.5};
cout << "=== 초기 학생 정보 ===" << endl;
printStudent(student1);
cout << "\\n=== 학점 업데이트 ===" << endl;
updateGPA(student1, 4.0);
cout << "\\n=== 업데이트된 학생 정보 ===" << endl;
printStudent(student1);
return 0;
}
참조 사용 시 주의사항
1. 참조는 반드시 초기화해야 함
// 올바른 사용
int number = 10;
int& ref = number; // 선언과 동시에 초기화
// 잘못된 사용
// int& ref; // 오류! 초기화 없는 참조 선언
2. 참조는 재할당 불가능
int a = 10;
int b = 20;
int& ref = a; // ref는 a의 별명
// ref = b; // 이것은 재할당이 아니라 a에 b의 값을 대입
ref = b; // a의 값이 20으로 변경됨
3. 상수 참조 (const reference)
#include <iostream>
using namespace std;
int main() {
int number = 100;
// 일반 참조
int& ref = number;
ref = 200; // 가능
// 상수 참조
const int& constRef = number;
// constRef = 300; // 오류! 상수 참조는 수정 불가
cout << "number: " << number << endl;
cout << "constRef: " << constRef << endl;
// 리터럴에 대한 상수 참조 (특별한 경우)
const int& literalRef = 42; // 가능!
cout << "literalRef: " << literalRef << endl;
return 0;
}
언제 참조를 사용할까?
참조를 사용하는 경우
- 함수 매개변수: 큰 객체를 복사하지 않고 전달
- 함수 반환값: 체이닝이나 직접 수정이 필요할 때
- range-based for 루프: 컨테이너 원소에 직접 접근
- 별명: 긴 변수명을 간단하게 만들 때
포인터를 사용하는 경우
- 동적 메모리 할당: new와 delete 사용
- null 값 필요: 유효하지 않은 상태 표현
- 재할당 필요: 다른 객체를 가리켜야 할 때
- 포인터 연산: 배열 순회나 복잡한 주소 계산
정리
핵심 개념 요약
- 참조: 변수의 별명, 안전하고 간편함
- 초기화: 선언과 동시에 반드시 초기화
- 재할당: 불가능, 한 번 정해지면 끝
- 메모리: 별도 공간 차지 안함
- 함수: 매개변수와 반환값으로 효율적 사용
참조 vs 포인터 선택 가이드
- 참조 선택: 안전하고 간단한 별명이 필요할 때
- 포인터 선택: 유연성과 동적 할당이 필요할 때
반응형
'Game DevTip > C++' 카테고리의 다른 글
13. C++ 스마트 포인터에 대해서 (1) | 2025.07.19 |
---|---|
12. C++ 배열에 대해서 (1) | 2025.07.16 |
10. C++ 포인터 기초 : 메모리 주소의 이해 (1) | 2025.07.15 |
9. 비트 연산과 Bitflag, Bitmask에 대해서 (6) | 2025.07.09 |
8. C++로 GameObjectPool을 구현 해보자. (1) | 2025.05.21 |
댓글