함수 개념·표준함수·사용자 정의 함수
학습 목표
- 함수의 필요성과 장단점 이해
- 표준 라이브러리 함수의 역할과 사용 방법 이해
- 사용자 정의 함수의 정의/호출/반환/원형(prototype) 개념 정복
1. 함수의 개념
1) 왜 함수인가
- 중복 제거: 같은 로직을 여러 번 쓰지 않고 재사용
- 가독성/유지보수: 큰 문제를 작은 작업 단위로 분할
- 재사용성: 다른 프로그램에서도 활용 가능
- 비용(단점): 호출/복귀 오버헤드가 존재
2) 함수란?
- 특정 작업을 수행하는 독립적인 코드 블록
- 호출 시 인수(Arguments) 를 전달 → 내부에서 처리 → 결괏값(Return Value) 을 돌려줌
- 프로그램은 여러 함수들의 조합으로 구성됨
3) 함수 호출 흐름(중첩 가능)
- main → f() → g(), h() … 식으로 다른 함수 호출 가능
- 호출이 끝나면 호출 지점으로 복귀(return)
2. 표준 함수(Standard Library)
1) 개념
- C가 제공하는 기본 기능 묶음(라이브러리)
- 헤더 파일에 함수 원형이 선언됨 → 사용 전 #include 필요
2) 대표 헤더와 주요 기능
- <stdio.h>: 입출력 printf, scanf, getchar, putchar …
- <string.h>: 문자열 strcat, strcmp, strcpy, strlen …
- <math.h>: 수학 sqrt, sin, cos, tan, log, fabs, pow, ceil, floor …
- <ctype.h>: 문자 분류/변환 isalpha, isdigit, tolower, toupper …
- <stdlib.h>: 유틸/변환/정렬 abs, rand, srand, atoi, strtol, qsort, bsearch …
3) 사용 예(발췌)
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main() {
double x = 12.34;
int i = -5, j = 2;
printf("abs(-5) = %d\n", abs(i));
printf("ceil(12.34) = %d\n", (int)ceil(x));
printf("floor(12.34) = %d\n", (int)floor(x));
printf("cos(10) = %f\n", cos(10));
printf("exp(2) = %f\n", exp(j));
printf("sqrt(2) = %f\n", sqrt(j));
printf("pow(4,2) = %d\n", (int)pow(4, 2));
}
문자 분류 예:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main() {
int i, alp = 0, no = 0, et = 0;
char s[64];
scanf("%63s", s);
for (i = 0; i < (int)strlen(s); i++) {
if (isalpha((unsigned char)s[i])) alp++;
else if (isdigit((unsigned char)s[i])) no++;
else et++;
}
printf("알파벳=%d, 숫자=%d, 기타=%d\n", alp, no, et);
}
3. 사용자 정의 함수(User-Defined Functions)
1) 함수 정의 형식
retType functionName(parameterList) { // 함수 헤더
// 지역변수 선언
// 실행문들
return expr; // retType이 void가 아니면 필요
}
- 반환 자료형(retType): 반환 값의 타입 (없으면 void)
- 함수명: 의미가 드러나는 이름 권장
- 매개변수(parameterList): 인수를 받는 형식 매개변수들
- 몸체 블록: 지역변수 선언 + 실행 코드 + 필요 시 return
2) 예제: 합 함수
#include <stdio.h>
int sum(int a, int b) {
int d = a + b;
return d;
}
int main() {
int x, y;
scanf("%d %d", &x, &y);
printf("%d\n", sum(x, y));
}
3) 매개변수와 인수
- 형식 매개변수: 함수 헤더에 선언된 변수(함수 내부에서 지역변수처럼 사용)
- 실 매개변수(인수): 호출 시 전달되는 값
- 타입/개수가 맞아야 하며, 다르면 묵시적 변환 또는 컴파일 에러
4) 반환(Return)
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) {
if (x > y) return y;
else return x;
}
- return을 만나면 즉시 복귀 + 값 반환
- void 함수는 return; 형태(값 없음) 또는 생략 가능
4. 함수의 원형(Prototype)과 선언 순서
1) 왜 원형이 필요한가
- 모든 이름은 사용 전에 선언되어야 함
- 호출 지점보다 뒤에 함수가 정의됐거나 다른 파일에 있으면,
컴파일러에게 반환형/매개변수 정보를 미리 알려야 함 → 원형 선언
2) 원형 선언 형태
int sum(int a, int b); // 매개변수 이름은 생략 가능
int sum(int, int); // 타입/개수 정보만으로도 충분
3) 올바른 배치 예
#include <stdio.h>
int sum(int a, int b); // 함수 원형
int main() {
int x, y;
scanf("%d %d", &x, &y);
printf("%d\n", sum(x, y)); // 뒤에 정의돼도 OK
}
int sum(int a, int b) { // 실제 정의
return a + b;
}
5. 실전 팁
- 헤더와 원형: 다른 파일의 함수를 사용한다면 헤더(.h) 를 만들고 원형을 그 안에 두고 #include 로 공유
- 이름/역할: 함수명은 동사+명사 조합으로 역할이 드러나게 (예: compute_min, read_line)
- 짧고 단일 책임: 하나의 함수는 하나의 명확한 일만 하도록
- 테스트 가능성: I/O와 로직을 분리하면 단위 테스트가 쉬움
- 표준함수 재사용: 문자열/수학/문자 처리 등은 표준 라이브러리부터 확인
6. 핵심 요약 체크리스트
- 함수는 독립적 코드 블록, 호출로 실행, return으로 복귀/반환
- 표준함수는 헤더 포함(stdio.h, string.h, math.h, ctype.h, stdlib.h 등)
- 사용자 정의 함수: 헤더(반환형/이름/매개변수) + 몸체 블록 + return
- 원형(prototype) 은 사용 전 선언을 보장
- 매개변수 타입·개수 일치 (필요 시 명시적 캐스팅 고려)
- void 반환: 값 없는 return; 또는 생략 가능
보너스: 예제 리팩터링(중복 제거)
초기 코드(세 값 최솟값 반복 계산)를 함수로 추출:
#include <stdio.h>
int minimum(int v1, int v2, int v3) {
int m = (v1 < v2) ? v1 : v2;
return (m < v3) ? m : v3;
}
int main() {
int a = 15, b = 10, c = 20;
int x = 25, y = 31, z = 17;
printf("%d, %d, %d의 최솟값 = %d\n", a, b, c, minimum(a, b, c));
printf("%d, %d, %d의 최솟값 = %d\n", x, y, z, minimum(x, y, z));
}
매개변수 전달, 유효범위(scope), 기억 클래스(storage class)
학습 목표
- 값 호출(Call by Value) 과 참조 호출(Call by Reference, 포인터 이용) 동작 차이를 정확히 이해한다.
- 유효범위(Scope) 와 존속기간(Lifetime) 을 구분하고, 지역/전역 변수의 우선순위를 이해한다.
- 기억 클래스(Storage Class) auto, static, extern, register의 의미와 사용 맥락을 습득한다.
1. 매개변수를 통한 자료 전달
1) 함수 호출 기본 흐름
- 호출: 인수(실 매개변수, actual parameter)를 함수로 전달
- 실행: 형식 매개변수(formal parameter)와 지역 변수로 계산
- 복귀: return 으로 값(있다면)을 호출자에게 반환
2) 값에 의한 전달 (Call by Value) — C의 기본
- 실 매개변수의 값을 복사해서 형식 매개변수로 받는다.
- 함수 내부에서 형식 매개변수를 바꿔도 호출자의 값은 그대로다.
#include <stdio.h>
int sum(int x, int y) { // x,y는 복사본
x += y;
return x;
}
int main() {
int a = 10, b = 20;
int d = sum(a, b);
printf("%d + %d = %d\n", a, b, d); // a,b는 변하지 않음
}
값 전달로는 swap 이 되지 않는 예:
#include <stdio.h>
void swap(int x, int y) { // 실패: 복사본만 교환
int temp = x;
x = y;
y = temp;
// 호출자 a,b에는 영향 없음
}
int main() {
int a = 3, b = 5;
swap(a, b);
printf("a=%d, b=%d\n", a, b); // 여전히 3, 5
}
3) 참조에 의한 전달 (Call by Reference) — 포인터로 구현
- 주소를 전달해 함수 내부에서 원본을 바꾸게 한다.
- C는 레퍼런스 타입을 직접 지원하지 않으므로 포인터 매개변수를 사용한다.
#include <stdio.h>
void swap(int *x, int *y) { // 성공: 원본 교환
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 3, b = 5;
swap(&a, &b); // 주소 전달
printf("a=%d, b=%d\n", a, b); // 5, 3
}
언제 포인터 전달을 쓰나?
- 호출자 값 수정이 필요할 때(결과를 매개변수로 되돌려야 할 때).
- 큰 구조체 등 복사 비용이 큰 인수를 효율적으로 전달할 때.
2. 유효범위(scope)와 존속기간(lifetime)
1) 핵심 정의
- 유효범위(Scope): 이름(변수/함수)을 사용할 수 있는 문법적 영역.
- 존속기간(Lifetime): 메모리에 존재하는 시간.
2) 지역변수(Local) vs 전역변수(Global)
- 지역변수: 블록 {} 또는 함수 내부에 선언. 해당 블록/함수 내에서만 사용.
- 전역변수: 모든 함수 밖에 선언. 프로그램 전체에서 접근(링크/가시성은 별도). 초기화하지 않으면 0.
이름이 같을 때의 우선순위(가려짐, name hiding):
- 가장 안쪽 블록의 지역변수가 우선한다.
#include <stdio.h>
int a = 10; // 전역
int main() {
int a = 20; // 지역(1)
{
int a = 30; // 지역(2)
printf("%d\n", a); // 30
}
printf("%d\n", a); // 20
}
권장: 가능한 지역변수를 사용(독립성↑, 디버깅 용이, 메모리 절약, 재귀에 유리).
3. 기억 클래스(Storage Class)
1) 개요
- 선언 시 지정 가능: 변수의 존속기간, 유효범위, 저장 위치 등에 영향.
- 주요 키워드: auto, static, extern, register.
2) auto — 자동 변수(기본)
- 지역변수의 기본 기억 클래스(대부분 생략).
- 블록 진입 시 생성 → 이탈 시 소멸.
- 초기화하지 않으면 쓰레기 값(정의되지 않은 값).
int main() {
int i = 1;
auto int j = 2; // 동일 효과
{ int i = 3; { int i = 4; /* 각각 독립 */ } }
}
3) static — 정적 변수
- 프로그램 시작~종료까지 메모리에 지속.
- 선언 위치에 따라:
- 함수/블록 내부 static: 유효범위는 그 블록이지만 값은 계속 유지.
- 파일 전역 static: 해당 소스 파일 내부에서만 보임(내부 연결, internal linkage).
- 초기화하지 않으면 0.
#include <stdio.h>
void test() {
auto int a = 0; // 호출할 때마다 0으로 다시 초기화
static int s = 0; // 한 번만 초기화, 값 유지
printf("auto=%d, static=%d\n", a, s);
++a; ++s;
}
int main() { test(); test(); test(); }
// 출력: auto=0,static=0 / auto=0,static=1 / auto=0,static=2
4) extern — 외부 변수(전역) 참조 선언
- 다른 위치(보통 다른 파일)의 전역변수를 사용하겠다는 선언.
- 전역 변수 그 자체의 정의는 한 번만. 다른 파일에서 쓸 때는 extern 으로 선언.
// a.c
int g = 10; // 전역변수 정의
// b.c
extern int g; // 전역변수 참조 선언
int main() { printf("%d\n", g); }
문자 배열을 여러 파일에서 공유하는 예:
// s_owner.c
char s[100]; // 정의
// s_user.c
extern char s[]; // 참조 선언
5) register — 레지스터 변수 요청
- CPU 레지스터(Central Processing Unit register) 사용을 요청(강제 아님).
- 매우 자주 쓰는 루프 카운터 등에서 성능 힌트로 사용.
- 유효범위/존속기간은 auto 와 동일. 주소 연산자 & 를 사용할 수 없을 수 있음(구현 의존).
int main() {
register int i;
int sum = 0;
for (i = 0; i <= 10; ++i) sum += i;
printf("%d\n", sum);
}
4. 핵심 정리 & 흔한 실수
핵심 정리
- 값 전달: 안전, 원본 불변. 참조 전달(포인터): 효율·양방향 데이터 전달.
- Scope vs Lifetime: 사용할 수 있는 영역 vs 메모리에 살아있는 시간.
- auto: 지역 기본, 진입~이탈. static: 한 번만 초기화, 값 유지.
- extern: 다른 번역 단위의 전역을 가져다 쓰기 위한 선언.
- register: 성능 힌트(요청), 필수 아님.
흔한 실수
- 값 전달로 swap 실패(포인터 써야 함).
- 전역/정적 변수 초기화 누락 가정(전역/정적은 0으로 초기화, 자동변수는 불특정 값).
- static 전역을 모듈 내부 한정으로 쓰는 의도(캡슐화)와 extern 공개의 혼동.
- 다른 파일 전역 사용 시 정의/선언 구분 실수(정의는 1회, 나머지는 extern 선언).
5. 연습 문제(개념 확인)
- 다음 코드에서 a, b 값은 어떻게 되는가?
void f(int x, int y) { x = y; y = x; }
int main(){ int a=1,b=2; f(a,b); printf("%d,%d\n",a,b); }
- 아래 코드 실행 결과는?
void g(){ static int s=0; printf("%d ", s++); }
int main(){ g(); g(); g(); }
- 두 파일 프로젝트에서 전역 변수 counter 를 공유하려면 각 파일에 무엇을 써야 하는가?
정답 힌트:
- 값 전달 → 1,2 / 2) 0 1 2 / 3) 한 파일에 int counter;(정의), 다른 파일에는 extern int counter;(선언)
- scope: 식별자가 유효한 코드 영역
- lifetime: 객체가 메모리에 존재하는 기간
- storage class: 변수의 저장 특성(존속기간·가시성·링크) 을 결정하는 분류
'C' 카테고리의 다른 글
| 배열과 포인터 (1) | 2025.11.11 |
|---|---|
| 선택 제어문과 반복 제어문 (0) | 2025.11.11 |
| 입.출력 함수와 연산자 (0) | 2025.10.22 |
| 자료형과 선행처리기 (0) | 2025.10.13 |
| C 언어의 개요 (1) | 2025.08.27 |