Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료

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