Topic 3: 참조와 함수 🔗⚡
🎯 학습 목표
- 참조를 함수 매개변수로 효과적으로 사용할 수 있다
- 참조 반환을 통한 함수 체이닝을 구현할 수 있다
- const 참조를 활용한 안전한 프로그래밍을 할 수 있다
- 대용량 객체 처리 시 성능 최적화를 적용할 수 있다
📖 참조 매개변수의 장점
참조 매개변수는 복사 비용 없이 원본 데이터에 직접 접근할 수 있게 해줍니다.
아날로지: 문서 작업 방식
- Call by Value: 문서를 복사해서 작업 (원본 안전하지만 비효율적)
- Call by Reference: 원본 문서에 직접 작업 (효율적이지만 신중해야 함)
🔍 성능 비교 실험
#include <iostream>
#include <chrono>
#include <string>
using namespace std;
using namespace std::chrono;
// 큰 구조체 (성능 테스트용)
struct LargeData {
string text[100]; // 큰 데이터
int numbers[100];
LargeData() {
for(int i = 0; i < 100; i++) {
text[i] = "Data " + to_string(i);
numbers[i] = i;
}
}
};
// Call by Value - 복사 발생
void processByValue(LargeData data) {
// 전체 구조체가 복사됨 (느림)
cout << "첫 번째 텍스트: " << data.text[0] << endl;
}
// Call by Reference - 복사 없음
void processByReference(const LargeData& data) {
// 복사 없이 원본 접근 (빠름)
cout << "첫 번째 텍스트: " << data.text[0] << endl;
}
int main() {
cout << "=== 성능 비교: Value vs Reference ===" << endl;
LargeData bigData;
// Call by Value 시간 측정
auto start = high_resolution_clock::now();
processByValue(bigData);
auto end = high_resolution_clock::now();
auto valueTime = duration_cast<microseconds>(end - start);
// Call by Reference 시간 측정
start = high_resolution_clock::now();
processByReference(bigData);
end = high_resolution_clock::now();
auto refTime = duration_cast<microseconds>(end - start);
cout << "\nCall by Value 시간: " << valueTime.count() << " microseconds" << endl;
cout << "Call by Reference 시간: " << refTime.count() << " microseconds" << endl;
cout << "성능 차이: " << (valueTime.count() - refTime.count()) << " microseconds" << endl;
return 0;
}
🔄 참조를 활용한 데이터 교환
#include <iostream>
#include <string>
using namespace std;
// 두 정수 교환
void swapInts(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 두 문자열 교환
void swapStrings(string& str1, string& str2) {
string temp = str1;
str1 = str2;
str2 = temp;
}
// 배열의 두 요소 교환
void swapArrayElements(int arr[], int index1, int index2, int size) {
if(index1 >= 0 && index1 < size && index2 >= 0 && index2 < size) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
int main() {
cout << "=== 참조를 활용한 데이터 교환 ===" << endl;
// 정수 교환
int x = 10, y = 20;
cout << "교환 전 정수: x=" << x << ", y=" << y << endl;
swapInts(x, y);
cout << "교환 후 정수: x=" << x << ", y=" << y << endl;
// 문자열 교환
string name1 = "철수", name2 = "영희";
cout << "\n교환 전 이름: name1=" << name1 << ", name2=" << name2 << endl;
swapStrings(name1, name2);
cout << "교환 후 이름: name1=" << name1 << ", name2=" << name2 << endl;
// 배열 요소 교환
int numbers[5] = {1, 2, 3, 4, 5};
cout << "\n교환 전 배열: ";
for(int i = 0; i < 5; i++) cout << numbers[i] << " ";
cout << endl;
swapArrayElements(numbers, 0, 4, 5); // 첫 번째와 마지막 교환
cout << "교환 후 배열: ";
for(int i = 0; i < 5; i++) cout << numbers[i] << " ";
cout << endl;
return 0;
}
🔄 참조 반환과 함수 체이닝
#include <iostream>
#include <string>
using namespace std;
// 전역 계산기 값
double calculatorValue = 0;
// 참조 반환으로 체이닝 가능한 함수들
double& add(double n) {
calculatorValue += n;
return calculatorValue;
}
double& subtract(double n) {
calculatorValue -= n;
return calculatorValue;
}
double& multiply(double n) {
calculatorValue *= n;
return calculatorValue;
}
double& divide(double n) {
if(n != 0) calculatorValue /= n;
return calculatorValue;
}
// 결과 출력
void showResult() {
cout << "결과: " << calculatorValue << endl;
}
// 계산기 초기화
void initCalculator(double initial = 0) {
calculatorValue = initial;
}
// 전역 문자열
string textData = "";
// 참조 반환으로 체이닝 가능한 문자열 함수들
string& appendText(const string& str) {
textData += str;
return textData;
}
string& prependText(const string& str) {
textData = str + textData;
return textData;
}
string& toUpperText() {
for(char& c : textData) {
if(c >= 'a' && c <= 'z') {
c = c - 32;
}
}
return textData;
}
void printText() {
cout << "문자열: \"" << textData << "\"" << endl;
}
// 문자열 초기화
void initText(const string& str = "") {
textData = str;
}
int main() {
cout << "=== 참조 반환과 함수 체이닝 ===" << endl;
// 계산기 체이닝
cout << "\n--- 계산기 체이닝 ---" << endl;
initCalculator(10);
// 체이닝: 10 + 5 = 15, 15 * 2 = 30, 30 - 3 = 27, 27 / 2 = 13.5
divide(subtract(multiply(add(5), 2), 3), 2);
showResult(); // 결과: 13.5
// 문자열 체이닝
cout << "\n--- 문자열 체이닝 ---" << endl;
initText("world");
toUpperText() = prependText("hello ") + "!";
printText(); // 결과: "HELLO WORLD!"
return 0;
}
🛡️ const 참조의 활용
#include <iostream>
#include <string>
using namespace std;
// 배열 크기
const int MAX_SIZE = 10;
// const 참조로 배열 읽기 전용 접근
void printArray(const int arr[], int size) {
cout << "배열 내용: ";
for(int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << "(크기: " << size << ")" << endl;
}
// const 참조로 문자열 처리
int countWords(const string& text) {
int count = 0;
bool inWord = false;
for(char c : text) {
if(c != ' ' && c != '\t' && c != '\n') {
if(!inWord) {
count++;
inWord = true;
}
} else {
inWord = false;
}
}
return count;
}
// 참조로 배열에 값 추가
void addValue(int arr[], int& size, int value) {
if(size < MAX_SIZE) {
arr[size] = value;
size++;
cout << "값 " << value << "이(가) 추가되었습니다." << endl;
} else {
cout << "배열이 가득 찼습니다." << endl;
}
}
int main() {
cout << "=== const 참조 활용 ====" << endl;
// 배열 테스트
int numbers[MAX_SIZE] = {1, 2, 3, 4, 5};
int size = 5;
printArray(numbers, size); // const 참조로 읽기만
addValue(numbers, size, 6); // 일반 참조로 수정
printArray(numbers, size);
// 문자열 분석
cout << "\n--- 문자열 분석 ---" << endl;
string text = "Hello World C++ Programming";
cout << "텍스트: \"" << text << "\"" << endl;
cout << "단어 수: " << countWords(text) << "개" << endl;
return 0;
}
🎯 실습: 간단한 학생 관리 시스템
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
double score;
void print() const {
cout << name << " (" << age << "세, " << score << "점)" << endl;
}
};
// const 참조로 정보 출력 (수정 불가)
void displayStudent(const Student& student) {
cout << "학생 정보: ";
student.print();
}
// 참조로 나이 증가
void celebrateBirthday(Student& student) {
student.age++;
cout << student.name << "의 생일! 나이가 " << student.age << "세가 되었습니다." << endl;
}
// 참조로 성적 향상
void improveScore(Student& student, double improvement) {
student.score += improvement;
if(student.score > 100) student.score = 100;
cout << student.name << "의 성적이 " << improvement << "점 향상되었습니다." << endl;
}
// 참조 반환으로 체이닝 지원
Student& updateName(Student& student, const string& newName) {
student.name = newName;
return student;
}
Student& updateAge(Student& student, int newAge) {
student.age = newAge;
return student;
}
int main() {
cout << "=== 참조를 활용한 학생 관리 ====" << endl;
Student student = {"김철수", 20, 85.5};
// const 참조로 정보 조회
displayStudent(student);
// 참조로 데이터 수정
cout << "\n--- 생일 축하 ---" << endl;
celebrateBirthday(student);
displayStudent(student);
cout << "\n--- 성적 향상 ---" << endl;
improveScore(student, 7.5);
displayStudent(student);
// 참조 반환으로 체이닝
cout << "\n--- 정보 업데이트 (체이닝) ---" << endl;
updateName(student, "김영수").updateAge(student, 22);
displayStudent(student);
return 0;
}
💡 참조 매개변수 vs 포인터 매개변수
#include <iostream>
#include <string>
using namespace std;
struct Person {
string name;
int age;
};
// 포인터 방식 - null 처리 가능
void updatePersonPointer(Person* person, const string& newName) {
if(person != nullptr) {
person->name = newName;
cout << "포인터로 이름 변경: " << person->name << endl;
} else {
cout << "null 포인터입니다!" << endl;
}
}
// 참조 방식 - 더 간단하고 안전
void updatePersonReference(Person& person, const string& newName) {
person.name = newName; // null 체크 불필요
cout << "참조로 이름 변경: " << person.name << endl;
}
// const 참조 - 읽기 전용
void printPersonInfo(const Person& person) {
cout << "이름: " << person.name << ", 나이: " << person.age << "세" << endl;
// person.age = 25; // 오류! const 참조로는 수정 불가
}
int main() {
cout << "=== 참조 vs 포인터 매개변수 ====" << endl;
Person person1 = {"철수", 20};
Person person2 = {"영희", 22};
// 초기 정보 출력
cout << "초기 정보:" << endl;
printPersonInfo(person1);
printPersonInfo(person2);
// 포인터 방식 테스트
cout << "\n--- 포인터 방식 ---" << endl;
updatePersonPointer(&person1, "김철수");
updatePersonPointer(nullptr, "테스트"); // null 처리
// 참조 방식 테스트
cout << "\n--- 참조 방식 ---" << endl;
updatePersonReference(person2, "이영희");
// 최종 정보 출력
cout << "\n최종 정보:" << endl;
printPersonInfo(person1);
printPersonInfo(person2);
return 0;
}
🔄 참조 반환의 실전 활용
#include <iostream>
using namespace std;
// 전역 카운터 변수
int counterValue = 0;
// 참조 반환으로 체이닝 지원하는 함수들
int& increment() {
counterValue++;
return counterValue;
}
int& decrement() {
counterValue--;
return counterValue;
}
int& reset() {
counterValue = 0;
return counterValue;
}
// 값 접근 함수
int getValue() {
return counterValue;
}
void displayCounter() {
cout << "카운터 값: " << counterValue << endl;
}
// 카운터 초기화
void initCounter(int initial = 0) {
counterValue = initial;
}
// 전역 카운터 (참조 반환 예제용)
int globalCounter = 0;
int& getGlobalCounter() {
return globalCounter; // 전역 변수 참조 반환 (안전)
}
int main() {
cout << "=== 참조 반환 활용 ====" << endl;
// 카운터 체이닝
cout << "--- 카운터 체이닝 ---" << endl;
initCounter(5);
displayCounter();
// 체이닝으로 연속 작업 (increment() → increment() → decrement() → increment())
increment(); increment(); decrement(); increment();
displayCounter();
// 전역 카운터 참조 반환
cout << "\n--- 전역 카운터 ---" << endl;
cout << "초기 전역 카운터: " << globalCounter << endl;
getGlobalCounter() = 100; // 참조 반환으로 직접 할당
cout << "할당 후: " << globalCounter << endl;
getGlobalCounter() += 50; // 참조 반환으로 연산
cout << "연산 후: " << globalCounter << endl;
return 0;
}
⚠️ 주의사항과 모범 사례
❌ 위험한 참조 반환
#include <iostream>
using namespace std;
// 위험한 예시 - 지역변수 참조 반환
int& dangerousReturn() {
int localVar = 42;
return localVar; // 위험! 함수 종료 후 localVar은 소멸
}
// 안전한 예시 - 매개변수나 전역변수 참조 반환
int& safeReturn(int& parameter) {
return parameter; // 안전! 매개변수는 함수 밖에서 살아있음
}
int main() {
cout << "=== 안전한 참조 반환 ====" << endl;
int value = 100;
// 안전한 참조 반환 사용
int& ref = safeReturn(value);
ref = 200;
cout << "value: " << value << endl; // 200으로 변경됨
// 위험한 참조 반환은 사용하지 말 것!
// int& badRef = dangerousReturn(); // 위험!
return 0;
}
✅ 모범 사례
#include <iostream>
#include <string>
using namespace std;
// 1. 큰 객체는 const 참조로 전달
void processLargeObject(const string& largeString) {
cout << "문자열 길이: " << largeString.length() << endl;
// 복사 비용 없음 + 수정 불가 = 안전하고 효율적
}
// 2. 수정이 필요한 경우만 일반 참조
void modifyString(string& str) {
str += " (수정됨)";
}
// 3. 체이닝이 필요한 경우 참조 반환
string& chainOperation(string& str) {
str += "!";
return str;
}
int main() {
cout << "=== 참조 사용 모범 사례 ====" << endl;
string message = "Hello World";
// const 참조로 안전한 읽기
processLargeObject(message);
// 일반 참조로 수정
modifyString(message);
cout << "수정 후: " << message << endl;
// 참조 반환으로 체이닝
chainOperation(message) += " 끝";
cout << "체이닝 후: " << message << endl;
return 0;
}
🧪 퀴즈 타임!
🤔 퀴즈 1: 참조 매개변수
다음 함수 호출 후 x의 값은?
void func(int& ref) {
ref = ref * 2 + 10;
}
int x = 5;
func(x);
정답: 20
ref는 x의 별명이므로 ref = 5 * 2 + 10 = 20이 되고, x도 20이 됩니다.
🤔 퀴즈 2: 성능 비교
다음 중 가장 효율적인 함수는?
struct BigData { int data[1000]; };
1. void func1(BigData data); // Call by Value
2. void func2(BigData* data); // Call by Address
3. void func3(const BigData& data); // Call by Reference
정답: 3번 (const 참조)
복사 비용이 없고, const로 안전성도 보장됩니다.
🎯 연습 문제
연습 1: 배열 요소 찾기
참조를 활용해서 배열에서 특정 값을 찾아 그 값을 수정하는 함수를 만들어 보세요.
💡 힌트
bool findAndReplace(int arr[], int size, int target, int newValue) {
// 배열에서 target을 찾아 newValue로 변경
// 찾으면 true, 못 찾으면 false 반환
}
연습 2: 구조체 배열 정렬
참조를 사용해서 구조체 배열을 정렬하는 함수를 만들어 보세요.
💡 힌트
void sortStudents(Student arr[], int size) {
// 버블 정렬로 성적순 정렬
// 교환할 때 참조 활용
}
🎉 마무리
참조는 C++의 핵심 기능 중 하나로, 안전하고 효율적인 프로그래밍을 가능하게 합니다!
오늘 배운 내용:
- 🔗 참조 매개변수의 성능상 이점
- 🔄 참조 반환을 통한 함수 체이닝
- 🛡️ const 참조의 안전성과 효율성
- ⚖️ 포인터와 참조의 적절한 선택 기준
다음 단계:
- Topic 4에서는 참조의 고급 활용법을 학습합니다
- 연산자 오버로딩과 참조의 관계를 다루게 됩니다
- 실전 프로젝트에서 참조를 효과적으로 활용하는 방법을 배우게 됩니다
참조를 잘 활용하면 더 안전하고 효율적인 C++ 프로그램을 작성할 수 있습니다! 💪
Last updated on