Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
C++ 프로그래밍Unit 8: 참조와 함수 고급Topic 2: 매개변수 전달 방식

Topic 2: 함수 매개변수 전달 방식 📦

🎯 학습 목표

  • call-by-value, call-by-address, call-by-reference의 차이를 이해할 수 있다
  • 각 전달 방식의 장단점을 파악하고 적절히 선택할 수 있다
  • 상황에 맞는 매개변수 전달 방식을 구현할 수 있다

🌟 왜 매개변수 전달 방식이 중요한가?

“함수 매개변수 전달 방식은 프로그램의 성능과 안정성을 결정하는 핵심 요소입니다!”

C++는 세 가지 매개변수 전달 방식을 제공합니다:

  1. Call by Value (값에 의한 전달)
  2. Call by Address (주소에 의한 전달 - 포인터)
  3. Call by Reference (참조에 의한 전달)

📚 세 가지 전달 방식 비교

🎯 핵심 비유

집에 친구를 초대한다고 생각해보세요:

  • Call by Value: 집 사진을 보여줌 (복사본)
  • Call by Address: 집 주소를 알려줌 (포인터)
  • Call by Reference: 집 열쇠를 직접 줌 (참조)

1️⃣ Call by Value (값에 의한 전달)

개념: 함수에 인자의 복사본을 전달합니다.

#include <iostream> using namespace std; // Call by Value - 원본은 변경되지 않음 void changeValue(int num) { num = 100; // 복사본을 변경 cout << "함수 내부: num = " << num << endl; } int main() { int original = 50; cout << "호출 전: original = " << original << endl; changeValue(original); cout << "호출 후: original = " << original << endl; // 여전히 50 return 0; }

출력:

호출 전: original = 50 함수 내부: num = 100 호출 후: original = 50

장단점

장점:

  • 원본 데이터 보호 (안전)
  • 함수 내에서 자유롭게 수정 가능
  • 이해하기 쉬움

단점:

  • 큰 객체 복사 시 성능 저하
  • 원본 수정 불가능

2️⃣ Call by Address (주소에 의한 전달 - 포인터)

개념: 함수에 변수의 주소를 전달합니다.

#include <iostream> using namespace std; // Call by Address - 포인터를 통한 원본 변경 void changeValueByPointer(int* ptr) { *ptr = 200; // 포인터가 가리키는 원본을 변경 cout << "함수 내부: *ptr = " << *ptr << endl; } // 두 값을 교환하는 예제 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } int main() { int original = 50; cout << "=== Call by Address 예제 ===" << endl; cout << "호출 전: original = " << original << endl; changeValueByPointer(&original); // 주소를 전달 cout << "호출 후: original = " << original << endl; // 200으로 변경됨 cout << "\n=== Swap 예제 ===" << endl; int x = 10, y = 20; cout << "교환 전: x = " << x << ", y = " << y << endl; swap(&x, &y); cout << "교환 후: x = " << x << ", y = " << y << endl; return 0; }

출력:

=== Call by Address 예제 === 호출 전: original = 50 함수 내부: *ptr = 200 호출 후: original = 200 === Swap 예제 === 교환 전: x = 10, y = 20 교환 후: x = 20, y = 10

장단점

장점:

  • 원본 수정 가능
  • 큰 객체도 주소만 전달 (효율적)
  • NULL 체크 가능

단점:

  • 포인터 문법 복잡 (*와 & 사용)
  • NULL 포인터 위험
  • 실수로 잘못된 메모리 접근 가능

3️⃣ Call by Reference (참조에 의한 전달)

개념: 함수에 변수의 **별칭(alias)**을 전달합니다.

#include <iostream> using namespace std; // Call by Reference - 참조를 통한 원본 변경 void changeValueByReference(int& ref) { ref = 300; // 참조를 통해 원본을 직접 변경 cout << "함수 내부: ref = " << ref << endl; } // 참조를 사용한 swap (더 간단한 문법) void swapByReference(int& a, int& b) { int temp = a; a = b; b = temp; } // const 참조 - 읽기 전용 void printValue(const int& ref) { cout << "값: " << ref << endl; // ref = 100; // 에러! const 참조는 수정 불가 } int main() { int original = 50; cout << "=== Call by Reference 예제 ===" << endl; cout << "호출 전: original = " << original << endl; changeValueByReference(original); // 그냥 변수를 전달 cout << "호출 후: original = " << original << endl; // 300으로 변경됨 cout << "\n=== Reference Swap 예제 ===" << endl; int x = 10, y = 20; cout << "교환 전: x = " << x << ", y = " << y << endl; swapByReference(x, y); cout << "교환 후: x = " << x << ", y = " << y << endl; cout << "\n=== Const Reference 예제 ===" << endl; printValue(original); // 읽기만 가능 return 0; }

출력:

=== Call by Reference 예제 === 호출 전: original = 50 함수 내부: ref = 300 호출 후: original = 300 === Reference Swap 예제 === 교환 전: x = 10, y = 20 교환 후: x = 20, y = 10 === Const Reference 예제 === 값: 300

장단점

장점:

  • 간단한 문법 (포인터보다 직관적)
  • 원본 수정 가능
  • NULL이 될 수 없음 (안전)
  • const로 읽기 전용 가능

단점:

  • 초기화 후 다른 변수 참조 불가
  • 함수 호출 시 원본 변경 여부가 명확하지 않음

🔍 세 가지 방식 종합 비교

#include <iostream> #include <string> using namespace std; struct Student { string name; int score; }; // 1. Call by Value void updateScoreByValue(Student s, int newScore) { s.score = newScore; // 복사본만 변경 cout << "By Value - 함수 내부: " << s.name << "의 점수 = " << s.score << endl; } // 2. Call by Address (Pointer) void updateScoreByPointer(Student* s, int newScore) { if (s != nullptr) { // NULL 체크 필요 s->score = newScore; // 원본 변경 cout << "By Pointer - 함수 내부: " << s->name << "의 점수 = " << s->score << endl; } } // 3. Call by Reference void updateScoreByReference(Student& s, int newScore) { s.score = newScore; // 원본 변경 cout << "By Reference - 함수 내부: " << s.name << "의 점수 = " << s.score << endl; } // 4. Const Reference (읽기 전용) void printStudent(const Student& s) { cout << "학생 정보: " << s.name << ", 점수: " << s.score << endl; // s.score = 100; // 에러! const 참조는 수정 불가 } int main() { cout << "=== 세 가지 전달 방식 비교 ===" << endl; // 1. Call by Value Student alice = {"Alice", 85}; cout << "\n[Call by Value]" << endl; cout << "호출 전: " << alice.name << "의 점수 = " << alice.score << endl; updateScoreByValue(alice, 95); cout << "호출 후: " << alice.name << "의 점수 = " << alice.score << endl; // 변경 안됨 // 2. Call by Address Student bob = {"Bob", 75}; cout << "\n[Call by Address]" << endl; cout << "호출 전: " << bob.name << "의 점수 = " << bob.score << endl; updateScoreByPointer(&bob, 90); // & 연산자로 주소 전달 cout << "호출 후: " << bob.name << "의 점수 = " << bob.score << endl; // 변경됨 // 3. Call by Reference Student charlie = {"Charlie", 80}; cout << "\n[Call by Reference]" << endl; cout << "호출 전: " << charlie.name << "의 점수 = " << charlie.score << endl; updateScoreByReference(charlie, 100); // 그냥 변수 전달 cout << "호출 후: " << charlie.name << "의 점수 = " << charlie.score << endl; // 변경됨 // 4. Const Reference (읽기 전용) cout << "\n[Const Reference - 읽기 전용]" << endl; printStudent(charlie); return 0; }

📊 언제 어떤 방식을 사용할까?

선택 가이드라인

상황추천 방식이유
기본 타입 (int, char, float)Call by Value복사 비용이 적음
큰 객체 읽기만const Reference복사 없이 안전하게 읽기
원본 수정 필요Reference 또는 Pointer직접 수정 가능
NULL 가능성 있음PointerNULL 체크 가능
배열 전달Pointer배열은 포인터로 전달
간단한 문법 원함Reference포인터보다 직관적

실무 예제: 효율적인 함수 설계

#include <iostream> #include <vector> #include <string> using namespace std; class DataProcessor { public: // 작은 값: Call by Value int square(int n) { return n * n; } // 큰 객체 읽기: Const Reference void printVector(const vector<int>& vec) { cout << "벡터 내용: "; for (int val : vec) { cout << val << " "; } cout << endl; } // 원본 수정: Reference void sortVector(vector<int>& vec) { // 간단한 버블 정렬 for (size_t i = 0; i < vec.size(); i++) { for (size_t j = 0; j < vec.size() - i - 1; j++) { if (vec[j] > vec[j + 1]) { swap(vec[j], vec[j + 1]); } } } } // 선택적 매개변수: Pointer (NULL 가능) void processData(vector<int>* data, string* log = nullptr) { if (data == nullptr) return; // 데이터 처리 for (int& val : *data) { val *= 2; } // 로그가 제공되면 기록 if (log != nullptr) { *log += "Data processed successfully\n"; } } }; int main() { DataProcessor processor; // 1. 작은 값 전달 cout << "5의 제곱: " << processor.square(5) << endl; // 2. 큰 객체 읽기 vector<int> numbers = {5, 2, 8, 1, 9}; processor.printVector(numbers); // 3. 원본 수정 processor.sortVector(numbers); cout << "정렬 후: "; processor.printVector(numbers); // 4. 선택적 매개변수 string log; processor.processData(&numbers, &log); cout << "처리 후: "; processor.printVector(numbers); cout << "로그: " << log << endl; return 0; }

💭 생각해보기

Q1. 왜 C++는 세 가지 방식을 모두 제공할까?

💡 답변

각 방식은 서로 다른 장점이 있습니다:

  • Call by Value: 안전성 (원본 보호)
  • Call by Pointer: 유연성 (NULL 허용, 동적 할당)
  • Call by Reference: 편의성 (간단한 문법)

프로그래머가 상황에 맞는 최적의 방식을 선택할 수 있도록 다양한 옵션을 제공합니다.

Q2. const reference는 언제 사용하나요?

💡 답변

큰 객체를 함수에 전달할 때, 복사 비용을 줄이면서도 원본을 보호하고 싶을 때 사용합니다. 예: void print(const vector<int>& vec)

Last updated on