1. 함수
함수는 기본적으로 다음과 같은 구조를 취한다.
1
2
3
4
5
6
|
int main()
{
}
|
cs |
4가지 부분으로 나누어 보면 다음과 같다.
int: 반환형.
main: 함수의 이름.
(): *매개변수의 형태. 위와 같이 아무것도 없는 것은 매개변수 없음을 의미한다.
{}: 함수의 몸체(body). 함수의 기능이 정의된다.
*매개변수: 인자라고도 하며, 함수를 실행할 때 그 함수로 전달하는 값을 말한다.
예를 들어 두 정수를 입력받아 더한 값을 반환하는 함수는 다음과 같이 선언한다.
1
2
3
4
|
int add(int num1, int num2)
{
return (num1 + num2);
}
|
cs |
함수를 호출하려면 다음과 같이 함수의 이름과 전달할 인자 값만 써주면 된다.
인자 값은 상수든 변수든 관계 없다.
1
|
add(3, 5);
|
cs |
2. 함수의 종류
함수는 크게 다음의 4가지 유형으로 나뉜다.
① 인자를 받고, 반환 값이 있는 함수
② 인자를 받지 않고, 반환 값이 있는 함수
③ 인자를 받고, 반환 값이 없는 함수
④ 인자를 받지 않고, 반환 값이 없는 함수
하나씩 보겠다.
① 인자를 받고, 반환 값이 있는 함수
앞에서 본 add 함수가 유형 ①의 예시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <stdio.h>
int add(int num1, int num2);
int main()
{
int a, b;
int result;
printf("두 정수 입력: ");
scanf("%d %d", &a, &b);
result = add(a, b);
printf("a + b = %d\n", result);
return 0;
}
int add(int num1, int num2)
{
return (num1 + num2);
}
|
cs |
이 간단한 예제는 많은 것을 시사하고 있다.
첫 번째로, 매개변수와 전달되는 변수의 이름이 다르다.
함수 호출 시 전달되는 것은 변수에 담긴 값이지, 변수 자체가 아니다.
그러니까 5 와 6 이 전달되는 것이지, a 와 b 가 전달되는 것이 아니라는 이야기다.
두 번째로, 3행과 19행에 주목하라.
3행을 함수의 선언(declaration)라 하고 19행을 함수의 정의(definitnion)라 한다.
둘의 차이점은 다음과 같다.
선언: 함수의 형태(반환형, 이름, 매개변수)
정의: 함수의 형태(반환형, 이름, 매개변수) + 함수의 기능(몸체)
그리고 이것을 컴파일러 입장에서 보면 다음과 같다.
선언: 매개변수로 두 정수를 받아서 정수를 반환하는 add라는 함수가 있으니까,
이 함수를 호출해도 에러로 해석하지 마렴.
정의: 매개변수로 두 정수를 받아서 정수를 반환하는 add라는 함수는
매개변수로 받은 두 정수의 합을 반환하는 함수야.
앞에서 선언하지 않고 코드 뒷부분에 바로 정의하면 에러가 발생한다.
컴파일러 입장에서는 add 라는 함수가 있다는 얘기는 들은 적이 없는데,
갑자기 add 함수를 호출해버리니 에러로 인식하는 것이다.
따라서 선언 없이 정의만 하고 싶다면 다음과 같이 코드 앞부분에서 해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int add(int num1, int num2)
{
return (num1 + num2);
}
int main()
{
int a, b;
int result;
printf("두 정수 입력: ");
scanf("%d %d", &a, &b);
result = add(a, b);
printf("a + b = %d\n", result);
return 0;
}
|
cs |
참고로 앞부분에서 함수를 선언할 때 다음과 같이 매개변수의 이름을 명시하지 않고 할 수도 있다.
1
|
int add(int, int);
|
cs |
물론 뒷부분에서 함수를 정의할 때는 매개변수의 이름을 명시해야 한다.
② 인자를 받지 않고, 반환 값이 있는 함수
1
2
3
4
|
int popluationOfKorea()
{
return 51811167;
}
|
cs |
한국의 인구 수(2019 통계청 기준)를 반환하는 함수다.
매개변수(인자)를 받지 않고 인구 수만 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int popluationOfKorea();
int main()
{
int popluation;
printf("대한민국의 인구 수는?");
popluation = popluationOfKorea();
printf("%d명입니다.");
return 0;
}
int popluationOfKorea()
{
return 51811167;
}
|
cs |
③ 인자를 받고, 반환 값이 없는 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#include <stdio.h>
void printBMI(double height, double weight);
int main()
{
double height;
double weight;
printf("키는 몇 m인가요? ");
scanf_s("%lf", &height);
printf("몸무게는 몇 kg인가요? ");
scanf_s("%lf", &weight);
printBMI(height, weight);
return 0;
}
void printBMI(double height, double weight)
{
double bmi = weight / (height * height);
printf("\n당신의 BMI 지수: %f\n", bmi);
if (bmi >= 30) { printf("비만이시네요!\n"); }
else if (bmi >= 25) { printf("과체중이시네요!\n"); }
else { printf("정상 체중이시네요!\n"); }
}
|
cs |
키와 몸무게는 임의로 입력했다.
참고로 반환 값이 없어도 return 문을 쓸 수 있다.
1
2
3
4
5
6
7
8
9
|
void onlyZero(int num)
{
if (num != 0)
{
return;
}
printf("0이 좋아\n");
}
|
cs |
return 문에는 두 가지 의미가 있다.
함수를 탈출한다는 의미와 값을 반환하다는 의미인데, 여기서는 전자의 의미만 살린 것이다.
반환값이 있는 함수에서의 return 문은 두 가지 의미를 모두 포함한다.
④ 인자를 받지 않고, 반환 값이 없는 함수
1
2
3
4
5
6
7
8
|
void whoAmI()
{
printf("나는 여러분보다 짧게 삽니다.\n");
printf("나는 네 발로 걸어다닙니다.\n");
printf("나는 피부가 단단합니다.\n");
printf("나는 털이 없습니다. 머리털도요.\n");
printf("나는 누구일까요?\n");
}
|
cs |
인자를 받지도 않고, 값을 반환하지도 않는다.
단지 출력만 할 뿐이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <stdio.h>
void whoAmI();
int main()
{
int answer;
whoAmI();
scanf_s("%d", &answer);
printf( (answer == 3) ? ("정답!\n") : ("오답!\n") );
return 0;
}
void whoAmI()
{
printf("나는 여러분보다 오래 삽니다.\n");
printf("나는 네 발로 걸어다닙니다.\n");
printf("나는 몸의 일부가 단단합니다.\n");
printf("나는 털이 없습니다. 머리털도요.\n");
printf("나는 누구일까요?\n");
printf("1. 거북이 2. 이구아나 3. 악어\n");
printf(">> ");
}
|
cs |
코드를 보면 알겠지만 답은 3. 악어다.
3. 함수를 사용하는 이유
이쯤에서 하나 의문이 생긴다.
함수는 대체 왜 사용하는 걸까?
함수로 구현하려면 선언도 해야하고, 함수 이름도 생각해야 하고, …
여러가지 귀찮은데, 그냥 함수 몸체에 쓸 내용을 바로 main 함수에서 쓰면 안 될까?
함수를 사용하는 이유는 크게 두 가지로 나뉜다.
① 코드의 재사용성
② Divide and Conquer
하나씩 보겠다.
① 코드의 재사용성
같은 코드가 반복될 경우 이를 반복해서 쓰는 것은 비효율적이라는 것이다.
포켓몬 게임의 세이브-로드 기능을 생각해보자.
먼저, 세이브가 되는 경우는 다양하다.
플레이어의 포켓몬이 모두 전투 불능 상태가 되었을 때, 플레이어가 직접 저장 버튼을 눌렀을 때,
그 외에도 여러가지 상황이 있을 것이다.
매 경우마다 세이브 코드를 작성해야 할까?
세이브 함수를 만들어서 매 경우마다 그 함수를 호출하는 게 더 편할 것이다.
세이브를 해야하는 경우가 총 5가지이고, 함수를 선언하지 않고 일일이 세이브 코드를
작성했다고 가정해보자. 그리고 세이브 코드에서 수정할 사항이 생겼다고 해보자.
5군데 모두 수정해야 한다. 이는 번거로운 일이 아닐 수 없다.
하지만 세이브 함수를 선언했다면 함수 정의 부분으로 이동해 거기서만 수정하면 된다.
프로그래밍을 할 때는 코드의 유지·보수 작업을 고려하는 것이 좋다.
② Divide and Conquer
분할하고 정복하라는 의미다.
**분할 정복 알고리즘이라고도 하는데, 이는 문제 해결의 원칙이 된다.
어떤 복잡한 문제가 닥쳤을 때, 우선 급한 불부터 끄고 나서 생각하자는 이야기를 들은 경험이
있을 것이다. 이것이 Divide and Conquer의 예시다.
크고 복잡한 문제를 작게 나눠서 하나씩 해결해 나가는 것이 문제 해결의 원칙이다.
따라서 프로그래밍에서도 하나씩 작은 기능으로 쪼개서 문제를 해결하는 것이다.
또 포켓몬 게임을 예로 들겠다. 과거 동아리 과제로 했던 경험을 토대로 이야기 하기 위함이다.
포켓몬 게임에는 다양한 기능이 있다.
시작 화면도 띄워야 하고, 스타트 포켓몬도 선택해야 되고, 세이브와 로드도 해야 하고,
상점도 가야 하고, 야생 포켓몬도 출몰해야 하고, …
이걸 main 함수 안에 한꺼번에 구현하려면 끔찍한 일이 아닐 수 없다.
그러니 함수로 쪼개는 것이다.
시작 화면을 띄우는 함수, 스타트 포켓몬을 선택받는 함수, 세이브 함수와 로드 함수,
상점을 가는 함수, 야생 포켓몬이 출몰하는 함수, …
그리고 이 함수들이 모여서 게임이라는 하나의 커다란 프로그램이 되는 것이다.
**분할 정복 알고리즘(Divide and conquer algorithm)
: 해결할 수 없는 문제를 작은 문제로 분할하여 문제를 해결하는 방법.
4. return 문이 여러 개인 함수
두 정수를 받아서 둘 중 더 큰 수를 반환하는 함수를 만들고 싶다고 해보자.
어떻게 해야 할까?
물론 다음과 같이 return 문 하나로도 가능하다(사실 필자는 이걸 더 선호한다).
1
2
3
4
|
int max(int a, int b)
{
return ((a >= b) ? a : b);
}
|
cs |
하지만 다음과 같이도 쓸 수 있다는 걸 알아두자.
return 문 여러 개를 써야만 하는 경우도 있으니 말이다.
1
2
3
4
5
6
7
|
int max(int a, int b)
{
if(a >= b)
return a;
else
return b;
}
|
cs |
다음은 3과 5, 7과 9 중에 큰 수를 출력하는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <stdio.h>
int max(int a, int b);
int main()
{
printf("3과 5 중에 큰 수는? %d\n", max(3, 5));
printf("7과 9 중에 큰 수는? %d\n", max(7, 9));
return 0;
}
int max(int a, int b)
{
if (a >= b)
return a;
else
return b;
}
|
cs |
매개변수 a 에 전달된 3 보다 매개변수 b 에 전달된 5 가 더 크므로 5 가 출력되었고,
매개변수 a 에 전달된 7 보다 매개변수 b 에 전달된 9 가 더 크므로 9 가 출력되었다.
함수가 자기 자신을 호출하는 재귀함수라는 것도 있지만, 그건 나중에 하는 게 낫다고 판단되어,
우선은 언급하지 않겠다.