시험공부

(2025-2) Introduction to programming(2) 5. Functions

norepinephrine 2025. 12. 18. 14:49

목표: 함수 정의/호출, 매개변수 전달 방식, 반환 규칙, 오버로딩, 선언/정의 분리(헤더), 가변 인자(initializer_list), 재귀, 포인터/배열 반환 규칙을 정확히 설명·구현한다.


✅ 학습 체크포인트 (Exam Scope)

  • [ ] 함수 구성요소(반환형/이름/매개변수/본문)와 호출 과정 설명 【】【】
  • [ ] 인자 평가 순서 비보장, 타입/개수 일치 규칙 【】
  • [ ] 지역/정적 지역 객체: 수명·초기화 타이밍 【】
  • [ ] 선언(프로토타입) vs 정의, 헤더에 선언 권장 【】
  • [ ] initializer_list에 의한 가변 개수 매개변수 【】
  • [ ] 반환 규칙: 값/참조, 로컬 객체 참조/포인터 반환 금지 【】【】
  • [ ] 참조 반환은 lvalue가 됨 【】
  • [ ] 재귀와 종료 경로 필요성 【】
  • [ ] “배열은 반환 불가, 배열에 대한 포인터/참조는 가능” + trailing return/decltype 【】
  • [ ] main의 커맨드라인 인자 개념 【】

1) 함수 기초

  • 함수 구성요소: 반환형, 이름, 매개변수 목록(0개 이상), 본문 【】
  • 호출 시 수행: (1) 인자로 매개변수 초기화, (2) 제어를 함수로 이동 → return을 만나면 종료 【】
  • 인자 규칙: 인자 평가 순서 비보장, 타입/개수 일치 필요 【】
// 팩토리얼(슬라이드 예시 주제): 값으로 받기
int fact(int n) {           // 반환형: int, 이름: fact, 파라미터: int n
    int res = 1;            // 지역 변수: 자동 저장기간
    for (int i = 2; i <= n; ++i) res *= i;
    return res;             // 반환 시 호출자 쪽 임시 객체로 초기화됨
}


2) 지역 객체와 수명

  • 스코프: 이름이 보이는 텍스트 영역 / 수명: 객체가 실제 존재하는 실행 구간 【】
  • 지역(자동) 객체: 블록 실행 중에만 존재, 함수 종료 시 파괴 【】
  • 정적 지역 객체: 첫 실행 때 1회 초기화, 프로그램 종료 때 파괴(캐시/누적용에 적합) 【】
int counter() {
    static int cnt = 0;   // 정적 지역: 최초 1회 0으로 값 초기화
    return ++cnt;         // 호출 누적: 1, 2, 3, ...
}


3) 선언(프로토타입)과 헤더, 분할 컴파일

  • 함수는 사용 전에 선언 필요. 정의는 한 번, 선언은 여러 번 가능(본문 없음; 세미콜론으로 마침) 【】
  • *헤더(.h)**에 선언(프로토타입), **소스(.cpp)**에 정의 권장 【】
// Math.h
int max3(int, int, int);   // 매개변수 이름 생략 가능 (프로토타입)

// Math.cpp
#include "Math.h"
int max3(int a, int b, int c) { return std::max(a, std::max(b, c)); }


4) 매개변수 전달: 값 / 참조 / (배열·포인터)

  • 값 전달: 매개변수는 복사본. 호출자 원본 불변.
  • 참조 전달: 별칭(원본 수정 가능). “읽기 전용”이면 const T& 사용.
  • 인자 타입/개수 일치 → 슬라이드 규칙 참고 【】
  • 배열 인수: C++ 언어 규칙상 함수 인자로 쓰면 포인터로 붕괴(decay). 크기정보는 사라지므로 크기/끝 반복자를 함께 전달하는 관례가 안전.
// (1) 값 vs 참조
void incr_val(int x) { ++x; }           // 원본 안 바뀜
void incr_ref(int& x) { ++x; }          // 원본 바뀜
void print_readonly(const int& x);      // 읽기 전용 참조

// (2) 배열 인수: 포인터로 decay → 끝을 함께 전달
void print_range(const int* first, const int* last) {
    for ( ; first != last; ++first) std::cout << *first << ' ';
}

주의: 인수 평가 순서가 정해져 있지 않으므로, 부수효과가 겹치는 표현식은 피하세요 【】.


5) 인자 수 가변: initializer_list

  • C++11의 initializer_list<T>가변 개수의 인자 처리 가능 (중괄호 리스트로 전달) 【】【】
#include <initializer_list>
int sum(std::initializer_list<int> xs) {
    int s = 0; for (int x : xs) s += x; return s;
}
int s = sum({1,2,3,4}); // 중괄호로 전달


6) 반환 규칙과 금기사항

  • 반환 타입 호환: 반환값은 함수 반환형과 같거나 묵시 변환 가능해야 함 【】
  • 어떻게 반환되나: 반환값은 호출 지점의 임시 객체 초기화로 전달(복사/이동 반환 최적화 가능) 【】
  • 참조 반환은 lvalue이므로 대입 대상이 될 수 있다 【】
  • 절대 금지: 로컬 객체(자동 저장기간)에 대한 참조/포인터를 반환하면 댕글링(미정의 동작) 【】
// BAD: 로컬 참조 반환 금지
const std::string& foo() {
    std::string s = "temp"; // 함수 끝나면 파괴
    return s;               // ❌ 댕글링 참조
}

// GOOD: 정적 지역 혹은 값 반환
const std::string& ok1() {
    static std::string s = "cache"; // 프로그램 종료 시 파괴
    return s;                       // ✅ 생존 보장
}
std::string ok2() { return "copy/move"; } // ✅ 값 반환


7) main과 커맨드라인 인자

  • 실행파일에 옵션/인자를 넘겨 동작을 제어할 수 있다(슬라이드 참고 설명) 【】
int main(int argc, char* argv[]) {
    // argc: 인자의 개수, argv: 인자 문자열 배열
    // argv[0]은 실행파일 이름
}


8) 재귀(Recursion)

  • 함수가 자기 자신을 호출하는 방식(직접/간접).
  • 항상 재귀를 빠져나오는 경로가 있어야 함(기저조건) 【】
int fib(int n){
    if (n<=1) return n;             // 기저 조건
    return fib(n-1) + fib(n-2);     // 재귀
}


9) “배열”을 직접 반환할 수 없고, 배열에 대한 포인터/참조는 가능

  • 배열은 복사 불가반환 타입이 될 수 없음.
  • 대신 배열 포인터/참조를 반환하거나, trailing return type / decltype로 선언을 단순화한다 【】.
// func는 정수형 매개변수 i를 받고, 정수 10개짜리 배열에 대한 포인터를 반환하는 함수이다.
int(*func(int i))[10];

// fcn은 int 인수를 받아 10개의 int 배열에 대한 포인터를 반환한다.
auto func(int i) -> int(*)[10];


10) 실전 패턴 & 예제

(A) “읽기 전용” 큰 객체는 const T& 로

#include <string>
size_t count_punct(const std::string& s) { // 복사비용↓, 수정X
    size_t c = 0;
    for (unsigned char ch : s) if (ispunct(ch)) ++c;
    return c;
}

(B) 범위 전달 관례(first/last)

void print(const int* first, const int* last) {
    for ( ; first != last; ++first) std::cout << *first << ' ';
}

(C) 정적 지역을 이용한 캐시

const std::string& banner() {
    static std::string b = "[Welcome]";
    return b; // 매 호출 시 같은 객체 참조 반환
}


11) 자주 틀리는 포인트 (체크리스트)

  • [ ] 인자 평가 순서 비보장 → 부수효과 있는 인자를 한 식에 여러 개 쓰지 않기 【】
  • [ ] 로컬 객체 참조/포인터 반환 금지(댕글링) 【】
  • [ ] 정적 지역은 1회 초기화/종료 시 파괴 → 누적/캐시 목적에 적합 【】
  • [ ] 헤더에 선언, 소스에 정의(분할 컴파일) 【】
  • [ ] 가변 인자는 initializer_list<T> 로(중괄호 전달) 【】
  • [ ] 배열 반환 불가 → 배열 포인터/참조 반환 패턴 사용 【】

12) 미니 퀴즈 (스스로 풀어보기)

  1. 아래 코드가 위험한 이유를 설명하고 고치세요.
const int& foo() { int x = 42; return x; }  // ?

해설: 로컬 객체 참조 반환 → 댕글링.

수정: static int x = 42; return x; 또는 int foo(){return 42;} 【】

  1. sum({1,2,3})처럼 호출하려면 어떤 시그니처여야 하나요?

int sum(std::initializer_list<int>) 【】

  1. 아래 선언을 말로 설명하세요.
auto f() -> int (*)[10];

해설: “int[10] 배열에 대한 포인터를 반환하는 함수” 【】