Ch.7 함수
함수는 C언어를 공부하면서 맞이하는 첫 번째 관문인 것 같다...
1) 함수의 작성과 사용
- 표준 라이브러리 함수
: C언어로 코드를 작성하면서 자연스럽게 사용하는 printf, scanf 등의 기능과 코드의 시작을 알리는 main 등 C 표준 라이브러리에서 기본으로 제공하는 함수.
특정 기능을 미리 약속하고 프로그램에서 바로 사용할 수 있게 이미 구현되어 있는 함수. 이러한 함수들은 '헤더' 파일에 포함되어 있음.
* 헤더 파일
: <stdio.h> 처럼 코드 작성 초기에 호출해서 접근할 수 있는 파일. 특정 기능을 하는 함수가 포함되어 있어 직접 해당 기능을 위한 함수를 구현하지 않아도 호출을 통해 그 기능을 사용할 수 있도록 편리를 제공함.
<stdio.h> - standard input output header. 표준 입출력과 관련된 함수를 포함하고 있는 헤더
<stdlib.h> - standard library header. 표준 유틸리티 함수를 포함하고 있는 헤더. 난수생성, 문자열 형식을 다른 형식으로 변환, 의사 난수 생성, 동적 메모리 관리 등의 함수를 포함하고 있다...고 한다.
- 함수 정의
: 표준 라이브러리 함수 기능에서 확장해서 내가 원하는 기능을 하도록 함수를 정의할 수 있다.
(1) 함수명 : 함수의 기능을 알아보기 쉬운 이름
(2) 매개변수 : 함수가 기능을 수행할 때 필요한 데이터
(3) 반환형 : 함수 결과의 자료형
반환형 함수명 (매개변수) 의 형태로 함수를 정의할 수 있음.
- 함수 선언
: 함수를 정의하고 사용하기 위해서, 컴파일러에 함수에 대한 정보를 전달하는 구문. 프로그램 상단에 함수 정의 부분을 작성.
- 함수 호출
: 코드 내에서 함수의 기능을 사용할 때, 실제 변수명을 매개변수 위치에 넣어서 작성하는 구문.
- 함수 반환
: 함수가 특정 기능을 수행한 후 나온 결과를, 함수를 호출한 위치로 전달해야 하는데, 이때 return이 사용됨.
return 되는 반환값은 함수를 정의할 때 설정한 반환형과 동일한 자료형이어야 함!
함수가 호출된 후 발생하는 반환값은 함수 호출부에 해당하기 때문에 반환값을 저장하는 변수가 필요함.
* 함수 정의에 사용하는 변수명은 그 함수 내에서만 유효한 지역 변수이기 때문에 다른 함수의 변수명과 동일해도 괜찮음
** 함수 호출 이전에 함수의 선언 혹은 함수의 정의가 있어야 함
(* 함수는 글로 이해하는 것보다 예제를 통해 어느 부분이 어떤 기능을 하는지 익히는 게 더 이해하기 쉬운 거 같음..!)
2) 여러 가지 함수 유형
(1) 매개 변수가 없는 함수
: 해당 함수가 특정 기능을 수행할 때 추가적인 데이터가 필요하지 않는 경우.
매개 변수의 위치에 void 사용
(2) 반환값이 없는 함수
: 해당 함수가 특정 기능을 수행한 후 결과를 어떤 변수에 저장할 필요가 없는 경우.
반환형을 void로 표현
* 둘 다 없는 경우도 있음
(3) 재귀 호출 함수(recursive call function)
: 해당 함수가 자기 자신을 다시 호출하는 함수.
'재귀적' 호출을 잘 사용하면 다양한 문제를 비교적 간단하게 풀 수 있음!
* 재귀 호출 함수를 호출할 때, 오버 플로우가 발생하지 않도록 함수를 정의해야 함.
= 함수 내부에서 함수를 호출할 때 사용되는 매개변수가 더 작은 수준으로 종료되어 0에 도달하거나, 종료 조건에 가까워지도록 입력되어야 오버 플로우에 빠지지 않을 수 있음. 오버 플로우가 발생하면 메모리 부족으로 인해 비정상적으로 종료됨.
* 재귀 호출 vs 반복문
: 반복문은 해당 코드 블럭 전체를 반복하지만, 재귀 호출 함수는 함수 내에서 재귀 호출된 부분까지만 반복됨.
반복문은 비교적 코드 전체를 파악하기 쉽지만, 재귀 호출 함수는 코드의 진행을 파악하기 쉽지 않음.
하지만 반복문을 재귀 호출 함수로 표현하면 더 간략한 코드로 나타낼 수 있음.
재귀 호출 함수는 함수가 호출될 때마다 새로운 메모리를 사용함.
< 예제 >
-1
#include <stdio.h>
int sum(int x, int y); //함수 선언
int main(){
int a = 10, b = 20;
int result;
result = sum(a, b); //함수 호출. 반환되는 값을 해당 함수의 반환형과 같은 변수에 저장함.
printf("result : %d\n", result);
return 0;
}
int sum(int x, int y){ //함수 정의
int temp;
temp = x + y;
return temp; //함수 반환값. 함수 정의 부분의 반환형과 동일한 자료형
}
-2
#include <stdio.h>
int get_num(void);
int main(){
int result;
result = get_num();
printf("반환값 : %d\n", result);
return 0;
}
int get_num(void){
int num;
printf("양수 입력: ");
scanf("%d", &num);
return num;
}
-3
#include <stdio.h>
void print_char(char ch, int count);
int main(){
print_char('@', 5);
return 0;
}
void print_char(char ch, int count){
int i;
for(i = 0; i < count; i++){
printf("%c", ch);
}
}
-4
#include <stdio.h>
void print_line(void);
int main(){
print_line();
printf("학번 이름 전공 학점\n");
print_line();
return 0;
}
void print_line(void){
int i;
for(i = 0; i < 50; i++){
printf("-");
}
printf("\n");
}
-5
#include <stdio.h>
void fruit(void);
int main(){
fruit();
return 0;
}
void fruit(void){
printf("apple\n");
fruit();
}
* 재귀 호출 함수 fruit()에서, 함수 호출이 종료되는 조건이 설정되어 있지 않고 계속 재귀 호출이 반복되므로 apple이 무한하게 출력될 것으로 예상되지만, 실제 출력 결과는 다음과 같다.

(보이지 않는 화면 위로 apple이 상당히 많이 출력되다가 종료됨)
무한히 출력되어야 하는 코드이지만 결국 종료하는 이유는, 해당 코드를 실행하기 위해 할당된 메모리를 모두 사용했기 때문이다. 컴퓨터에서 하드웨어가 특정 프로그램을 실행하기 위해서는 일정 메모리가 필요한데, 컴퓨터의 메모리는 무한하지 않기 때문에 함수를 재귀적으로 호출하면서 사용되어야 하는 메모리가 부족해지면 강제 종료된다. 이러한 현상을 오버플로우라고 부르며 비정상적인 종료이므로 '오류'가 발생한 것이다.
-6
#include <stdio.h>
void fruit(int count);
int main(){
fruit(1);
return 0;
}
void fruit(int count){
printf("apple\n");
if(count == 3) return; //재귀 호출을 멈추는 조건
fruit(count + 1); // 재귀 호출 부분
}
* 코드를 작성하는 사람이 의도하는 대로 진행되도록 하기 위해서는 오버플로우와 같은 오류를 방지하고, 발생한 경우 디버깅해야 한다.
재귀 호출 함수에서 재귀 호출을 하는 재귀적 조건과 재귀 호출을 그만두는 종료 조건을 설정하는 것이 필수다.
이때, 재귀 호출 코드에서 사용되는 매개변수가 종료 조건에 가까워지도록 설정해야 한다.
-7
#include <stdio.h>
void fruit(int count);
int main(){
fruit(1);
return 0;
}
void fruit(int count){
printf("apple\n");
if(count == 3) return;
fruit(count + 1);
printf("jam\n");
}
* 재귀 호출 함수에서, 재귀 호출부는 연쇄적으로 실행된다. 즉 위에서 아래로 한 줄씩 실행되다가, 재귀 호출부에 도달하면 재귀 호출이 진행되고 재귀 호출 과정이 종료된 후 다시 이전 함수로 돌아가서 그 다음 코드를 실행한다... 글로 설명하면 이해하기 힘드니 위 코드의 실행 과정을 그림으로 나타내보겠다.

위 그림에서처럼, 함수 내부에서 호출된 재귀 함수 부분에서는 반환되면 처음에 함수가 호출된 main함수로 돌아가는 것이 아니라, 해당 재귀 함수가 호출되었던 부분으로 돌아가서 그 다음 줄의 코드가 실행된다.
< 확인 문제 >
-1 두 실수의 평균을 구하는 함수 호출 코드
#include <stdio.h>
double average(double a, double b);
int main(){
double res;
res = average(1.5, 3.4);
printf("%.2lf", res);
}
double average(double a, double b){
double temp;
temp = a + b;
return (temp / 2.0);
}
-2 187cm를 미터 단위로 환산하는 코드
#include <stdio.h>
double centi_to_meter(double cm);
int main(){
double res;
res = centi_to_meter(187);
printf("%.2lfm\n", res);
return 0;
}
double centi_to_meter(double cm){
double m;
m = cm / 100.0;
return m;
}
-3 sum 함수 완성하기
#include <stdio.h>
void sum(int n);
int main(){
sum(10);
sum(100);
return 0;
}
void sum(int n){
int res = 0;
for(int i = 1; i <= n; i++){
res += i;
}
printf("%d\n", res);
}
- 도전 실전 예제
1부터 10까지의 합을 구하는 sum 함수를 재귀 호출 함수로 구현하시오
#include <stdio.h>
int rec_func(int n);
int main(){
int sum;
sum = rec_func(10);
printf("%d", sum);
}
int rec_func(int n){
int sum = n;
int t;
if(n == 1) return n;
t = rec_func(n-1);
sum = sum + t;
return sum;
}