시험공부
(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) 미니 퀴즈 (스스로 풀어보기)
- 아래 코드가 위험한 이유를 설명하고 고치세요.
const int& foo() { int x = 42; return x; } // ?
해설: 로컬 객체 참조 반환 → 댕글링.
수정: static int x = 42; return x; 또는 int foo(){return 42;} 【】
- sum({1,2,3})처럼 호출하려면 어떤 시그니처여야 하나요?
int sum(std::initializer_list<int>) 【】
- 아래 선언을 말로 설명하세요.
auto f() -> int (*)[10];
해설: “int[10] 배열에 대한 포인터를 반환하는 함수” 【】