함수와 재귀

2025. 3. 27. 23:43Programming Languages/C++

함수와 재귀

함수는 C++ 프로그래밍의 핵심 요소로, 코드의 모듈화, 재사용성, 가독성을 향상시킵니다. 이 챕터에서는 함수의 기본 개념부터 고급 기능까지 심층적으로 살펴보겠습니다.

4.1 함수의 기본

4.1.1 함수의 구조

C++에서 함수는 다음과 같은 구조로 이루어져 있습니다:

반환_타입 함수_이름(매개변수_목록) {
    // 함수 본문
    return 반환_값;  // 있는 경우
}

예제: 간단한 함수

#include <iostream>

// 두 정수의 합을 반환하는 함수
int add(int a, int b) {
    int sum = a + b;
    return sum;
}

int main() {
    int result = add(5, 3);
    std::cout << "5 + 3 = " << result << std::endl;
    
    return 0;
}

함수의 주요 구성 요소:

  1. 반환 타입(Return Type): 함수가 작업을 완료한 후 반환하는 값의 데이터 타입
  2. 함수 이름(Function Name): 함수를 식별하는 고유한 이름
  3. 매개변수 목록(Parameter List): 함수가 받는 입력 값들
  4. 함수 본문(Function Body): 함수가 수행하는 작업을 정의하는 코드 블록
  5. 반환 문(Return Statement): 함수의 결과를 호출자에게 돌려주는 구문

4.1.2 함수 선언과 정의

C++에서는 함수 선언(Declaration)과 정의(Definition)를 분리할 수 있습니다:

  • 함수 선언(프로토타입): 함수의 인터페이스(반환 타입, 이름, 매개변수 타입)만 컴파일러에 알려줍니다.
  • 함수 정의: 함수의 실제 구현 코드를 포함합니다.

예제: 함수 선언과 정의 분리

#include <iostream>

// 함수 선언 (프로토타입)
int multiply(int x, int y);

int main() {
    int result = multiply(4, 5);
    std::cout << "4 × 5 = " << result << std::endl;
    return 0;
}

// 함수 정의
int multiply(int x, int y) {
    return x * y;
}

함수 선언의 장점:

  • 컴파일러가 함수 호출을 검증할 수 있음
  • 함수 정의 순서에 상관없이 함수 사용 가능
  • 코드 조직화 및 헤더 파일 활용에 유용

4.1.3 반환 타입(Return Type)

함수는 다양한 타입의 값을 반환할 수 있습니다:

기본 타입 반환

int getAge() {
    return 25;
}

double getPI() {
    return 3.14159265359;
}

char getGrade() {
    return 'A';
}

bool isValid() {
    return true;
}

void 반환 타입

반환 값이 없는 함수는 void 타입을 사용합니다:

void printMessage(std::string message) {
    std::cout << message << std::endl;
    // return 문 필요 없음
}

배열 반환

C++에서 함수는 직접 배열을 반환할 수 없지만, 포인터나 참조를 사용하거나 구조체/클래스를 통해 간접적으로 반환할 수 있습니다:

#include <iostream>
#include <array>

// 정적 배열을 사용할 경우
int* getArray() {
    static int arr[5] = {1, 2, 3, 4, 5};  // static 키워드 필요
    return arr;
}

// std::array 사용 (권장)
std::array<int, 5> getModernArray() {
    return {1, 2, 3, 4, 5};
}

int main() {
    int* arr = getArray();
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    auto modernArr = getModernArray();
    for (int num : modernArr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

문자열 반환

#include <iostream>
#include <string>

std::string getGreeting(const std::string& name) {
    return "Hello, " + name + "!";
}

int main() {
    std::string greeting = getGreeting("Alice");
    std::cout << greeting << std::endl;  // Hello, Alice!
    
    return 0;
}

4.1.4 매개변수(Parameters)

함수에 데이터를 전달하는 방법입니다:

값에 의한 전달(Pass by Value)

void incrementByValue(int x) {
    x++;  // 매개변수 x의 복사본 수정
    std::cout << "함수 내부 x: " << x << std::endl;
}

int main() {
    int num = 5;
    incrementByValue(num);
    std::cout << "함수 호출 후 num: " << num << std::endl;  // 여전히 5
    
    return 0;
}

참조에 의한 전달(Pass by Reference)

void incrementByReference(int& x) {
    x++;  // 원본 변수 직접 수정
    std::cout << "함수 내부 x: " << x << std::endl;
}

int main() {
    int num = 5;
    incrementByReference(num);
    std::cout << "함수 호출 후 num: " << num << std::endl;  // 6으로 변경됨
    
    return 0;
}

상수 참조(Const Reference)

참조로 전달하되 수정은 방지하는 방법입니다:

void printInfo(const std::string& str) {
    // str = "변경";  // 컴파일 오류: 상수 참조는 수정 불가
    std::cout << "문자열 길이: " << str.length() << std::endl;
}

int main() {
    std::string message = "Hello, World!";
    printInfo(message);  // 복사 없이 효율적으로 전달
    
    return 0;
}

포인터에 의한 전달(Pass by Pointer)

void incrementByPointer(int* ptr) {
    if (ptr != nullptr) {  // null 체크 중요
        (*ptr)++;  // 역참조하여 값 수정
    }
}

int main() {
    int num = 5;
    incrementByPointer(&num);  // 주소 전달
    std::cout << "함수 호출 후 num: " << num << std::endl;  // 6
    
    return 0;
}

다양한 타입의 매개변수

#include <iostream>
#include <string>
#include <vector>

void displayInfo(std::string name, int age, double height, bool isStudent) {
    std::cout << "이름: " << name << std::endl;
    std::cout << "나이: " << age << "세" << std::endl;
    std::cout << "키: " << height << "cm" << std::endl;
    std::cout << "학생 여부: " << (isStudent ? "예" : "아니오") << std::endl;
}

// 벡터 매개변수
double getAverage(const std::vector<int>& numbers) {
    if (numbers.empty()) {
        return 0.0;
    }
    
    double sum = 0.0;
    for (int num : numbers) {
        sum += num;
    }
    
    return sum / numbers.size();
}

int main() {
    displayInfo("홍길동", 25, 175.5, true);
    
    std::vector<int> scores = {85, 92, 78, 90, 88};
    std::cout << "평균 점수: " << getAverage(scores) << std::endl;
    
    return 0;
}

4.1.5 기본 매개변수(Default Parameters)

매개변수에 기본값을 지정하여 선택적 인자로 만들 수 있습니다:

#include <iostream>
#include <string>

// 모든 기본 매개변수는 오른쪽에서부터 지정해야 함
void printMessage(std::string message, int count = 1, bool addNewLine = true) {
    for (int i = 0; i < count; i++) {
        std::cout << message;
        if (addNewLine) {
            std::cout << std::endl;
        }
    }
}

int main() {
    printMessage("Hello");  // 기본값 사용: count=1, addNewLine=true
    printMessage("Hi", 3);  // 기본값 사용: addNewLine=true
    printMessage("No newline", 1, false);  // 모든 매개변수 지정
    
    return 0;
}

주의사항:

  • 기본 매개변수는 함수 선언에만 지정해야 함 (정의에는 반복하지 않음)
  • 모든 기본 매개변수는 오른쪽에서부터 배치해야 함

4.1.6 함수 오버로딩(Function Overloading)

같은 이름의 함수를 매개변수 타입이나 개수를 다르게 하여 여러 개 정의할 수 있습니다:

#include <iostream>
#include <string>

// 정수 두 개 더하기
int add(int a, int b) {
    std::cout << "정수 버전 호출" << std::endl;
    return a + b;
}

// 부동 소수점 두 개 더하기
double add(double a, double b) {
    std::cout << "실수 버전 호출" << std::endl;
    return a + b;
}

// 세 개의 정수 더하기
int add(int a, int b, int c) {
    std::cout << "세 정수 버전 호출" << std::endl;
    return a + b + c;
}

// 문자열 연결
std::string add(const std::string& a, const std::string& b) {
    std::cout << "문자열 버전 호출" << std::endl;
    return a + b;
}

int main() {
    std::cout << add(5, 3) << std::endl;         // 정수 버전
    std::cout << add(3.5, 2.5) << std::endl;     // 실수 버전
    std::cout << add(1, 2, 3) << std::endl;      // 세 정수 버전
    std::cout << add("Hello, ", "World!") << std::endl;  // 문자열 버전
    
    return 0;
}

함수 오버로딩의 규칙:

  • 반환 타입만 다른 경우는 오버로딩 불가
  • 기본 매개변수만 다른 경우도 오버로딩 불가 (모호성 발생)
  • 함수 이름과 매개변수의 타입/개수로 함수를 구분

4.1.7 인라인 함수(Inline Functions)

작은 함수를 호출 오버헤드 없이 실행하기 위한 최적화 기법입니다:

#include <iostream>

// 인라인 함수 선언
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 5, y = 7;
    
    // 컴파일러는 함수 호출 대신 코드를 직접 삽입할 수 있음
    // 아래 코드는 다음과 같이 처리될 수 있음: int result = (x > y) ? x : y;
    int result = max(x, y);
    
    std::cout << "최대값: " << result << std::endl;
    
    return 0;
}

인라인 함수의 특징:

  • inline 키워드는 컴파일러에 대한 힌트일 뿐, 반드시 인라인화되는 것은 아님
  • 작고 자주 호출되는 함수에 적합
  • 헤더 파일에 완전한 정의가 필요
  • 재귀 함수나 큰 함수는 인라인화에 적합하지 않음

 

'Programming Languages > C++' 카테고리의 다른 글

재귀(Recursion)  (0) 2025.03.27
함수 포인터와 함수 객체  (0) 2025.03.27
챕터3. 실습문제  (0) 2025.03.27
조건부 컴파일  (0) 2025.03.27
제어 흐름 변경  (0) 2025.03.27