프로그램을 짜다 보면 같은 계산과 출력을 여러 번 되풀이하게 됩니다. 똑같은 코드를 복사해 붙이면 당장은 돌아가지만, 나중에 그 부분을 고칠 때 붙여 넣은 자리를 전부 찾아 바꿔야 하고 하나라도 놓치면 프로그램이 어긋납니다. 함수는 이 반복을 없애려고 만든 도구로, 자주 쓰는 작업 묶음에 이름을 붙여 두고 필요할 때 그 이름만 부릅니다. 여기서는 함수를 만드는 정의와 부르는 호출, 값을 받는 매개변수와 넣는 인자, 결과를 돌려받는 반환값을 예제로 익혀 봅니다.
12.1 함수는 이름 붙인 작업 묶음
커피 자판기를 떠올리면 함수의 구조가 대부분 설명됩니다. 버튼에는 이름이 붙어 있어, 안에서 물을 끓이고 원두를 내리는 과정을 몰라도 버튼만 누르면 됩니다. 동전을 넣으면 그 값에 따라 다른 음료가 나오고, 완성된 커피가 밖으로 나옵니다. 함수도 똑같아서, 이름이 있고(버튼), 값을 넣는 자리가 있고(동전 투입구), 정해진 일을 한 뒤 결과를 하나 돌려줍니다(나오는 커피). 한 번 잘 만들어 두면 그 안을 매번 신경 쓰지 않고 이름만 불러 씁니다.
12.2 정의와 호출
가장 단순한 함수부터 만들어 봅니다. 인사말을 찍는 일을 greet라는 이름으로 묶어 둡니다.
#include <stdio.h>
void greet(void) {
printf("어서 오세요!\n");
}
int main(void) {
greet(); // 호출
greet();
greet();
return 0;
}
실행 결과:
어서 오세요!
어서 오세요!
어서 오세요!
![[C 12] 함수 기초 - 정의와 호출](https://img.thenullpage.com/posts/6547/6547_2_e8becb.webp)
함수 정의는 네 부분입니다. 맨 앞 void는 돌려주는 값이 없다는 반환형, greet가 함수 이름, 괄호 안 void는 받는 값이 없다는 뜻, 중괄호 안이 실제로 하는 일(몸통)입니다. main에서 greet();처럼 이름 뒤에 괄호를 붙여 부르는 것이 호출입니다. 호출할 때마다 몸통이 처음부터 다시 실행되므로 세 번 부르면 세 줄이 찍히고, 인사말을 바꿀 때도 함수 몸통 한 줄만 고치면 세 곳이 모두 바뀝니다. 이것이 반복을 함수로 묶었을 때의 이득입니다.
12.3 값을 돌려주는 함수와 return
이번에는 결과를 돌려받는 함수입니다. 두 정수를 더해 그 합을 내주는 add를 만듭니다.
#include <stdio.h>
int add(int a, int b) { // 두 정수를 받아 합을 돌려줌
return a + b;
}
int main(void) {
printf("%d\n", add(3, 4));
printf("%d\n", add(10, 20));
return 0;
}
실행 결과:
7
30
괄호 안의 int a, int b가 매개변수, 곧 함수가 바깥에서 값을 받아 둘 자리입니다. return a + b는 계산한 값을 호출한 곳으로 돌려보내라는 명령이고, 맨 앞 int는 돌려주는 값이 정수라는 반환형입니다. main에서 add(3, 4)라고 쓰면 그 자리가 통째로 7로 바뀌어 printf가 7을 찍습니다.
주의. 반환형과 실제 돌려주는 값의 타입은 맞아야 합니다. 반환형을 int로 적고 return을 빠뜨리면 컴파일러가 경고하거나 쓰레기 값이 넘어옵니다. 또 return을 만나는 순간 함수는 즉시 끝나므로, 그 아래 코드는 실행되지 않습니다.
12.4 매개변수는 자리, 인자는 넣는 값
![[C 12] 함수 기초 - 정의와 호출 (2)](https://img.thenullpage.com/posts/6547/6547_3_2b7b6a.webp)
매개변수와 인자는 자주 헷갈리는 한 쌍입니다. 매개변수(parameter)는 함수를 정의할 때 괄호 안에 두는 빈 자리이고, 인자(argument)는 함수를 부를 때 그 자리에 넣는 실제 값입니다. 택배 상자의 빈 운송장 칸이 매개변수, 거기 적어 넣는 주소가 인자인 셈이지요. 앞의 add를 그대로 두고 넣는 값만 바꿔 봅니다.
int x = 10, y = 5;
printf("%d\n", add(3, 4)); // 값을 직접
printf("%d\n", add(x, y)); // 변수를 넘김
printf("%d\n", add(x, y * 2)); // 계산식도 가능
실행 결과:
7
15
20
같은 add 함수라도 넣는 인자에 따라 결과가 달라집니다. 값을 직접 써도, 변수를 넘겨도, y * 2 같은 계산식을 넘겨도 되며, 넘어가는 순간 그 값이 매개변수 a, b에 복사됩니다.
주의. 함수가 받는 것은 값의 사본입니다. 함수 안에서 매개변수 a를 바꿔도 바깥의 원래 변수 x는 그대로 남습니다. 원본을 바꾸려면 값이 아니라 변수의 주소를 넘겨야 하는데, 그 방식은 별도로 다룹니다.
12.5 인자에 따라 달라지는 결과, 배송비 계산
함수의 진짜 쓸모는 넘긴 값에 따라 다른 결과를 낼 때 드러납니다. 거리를 받아 배송비를 정하는 함수를 봅니다.
#include <stdio.h>
int shippingFee(int distance) {
if (distance > 100) return 5000;
return 2500;
}
int main(void) {
printf("서울: %d원\n", shippingFee(30));
printf("제주: %d원\n", shippingFee(400));
return 0;
}
실행 결과:
서울: 2500원
제주: 5000원
shippingFee에 30을 넣으면 조건 distance > 100이 거짓이라 2500을, 400을 넣으면 참이라 5000을 돌려줍니다. 배송비 규칙이 바뀌어도 이 함수 하나만 고치면 되고, 부르는 모든 곳이 자동으로 새 규칙을 따릅니다.
12.6 두 값 중 큰 쪽 고르기
자주 필요한 최댓값 고르기도 함수로 묶어 두면 편합니다. 두 값을 받아 큰 쪽을 돌려주는 maxOf입니다.
#include <stdio.h>
int maxOf(int a, int b) {
return (a > b) ? a : b;
}
int main(void) {
printf("큰 값: %d\n", maxOf(15, 27));
return 0;
}
실행 결과:
큰 값: 27
(a > b) ? a : b는 삼항 연산자로, a가 b보다 크면 a를 아니면 b를 고릅니다. 큰 쪽을 골라 return으로 돌려주므로, 두 값을 비교할 때마다 긴 if 문을 다시 쓰지 않고 maxOf만 부르면 됩니다.
12.7 작은 함수를 조립하기
함수 안에서 다른 함수를 부를 수도 있습니다. 곱셈 함수를 하나 만들어 두고, 사각형 넓이 함수가 그것을 가져다 쓰는 예입니다.
#include <stdio.h>
int multiply(int a, int b) {
return a * b;
}
int rectArea(int w, int h) {
return multiply(w, h); // 다른 함수 호출
}
int main(void) {
printf("넓이: %d\n", rectArea(4, 5));
return 0;
}
실행 결과:
넓이: 20
rectArea는 넓이를 직접 계산하지 않고 multiply에게 맡깁니다. 이렇게 작은 함수를 여러 개 만들어 조립하면, 함수 하나가 한 가지 일만 하니 읽기 쉽고, 곱셈 방식을 바꿀 일이 생겨도 multiply 한 곳만 손대면 됩니다.
12.8 함수 원형, 정의가 아래에 있어도 부르기
지금까지는 부르는 함수를 항상 main 위에 두었습니다. C 컴파일러는 소스를 위에서 아래로 읽으며, 아직 보지 못한 이름을 만나면 어떤 함수인지 모르기 때문입니다. 정의를 main 아래에 두려면 함수 원형(프로토타입)을 미리 알려 줍니다.
#include <stdio.h>
int square(int x); // 원형 선언(먼저 알려주기)
int main(void) {
printf("%d\n", square(6));
return 0;
}
int square(int x) { // 실제 정의는 아래에
return x * x;
}
실행 결과:
36
int square(int x); 한 줄이 원형 선언입니다. 이름, 매개변수 타입, 반환형만 알려 주고 몸통은 생략한 채 세미콜론으로 끝냅니다. 미리 신고해 두면 컴파일러가 아래에 실제 정의가 있으리라 믿고 넘어갑니다.
함정. 원형도 없이 정의를 main 아래에 두고 부르면, 오래된 C에서는 반환형을 멋대로 int로 가정하고 인자 타입도 검사하지 않아 조용한 오류로 이어집니다. 함수가 여러 개로 늘어나면 원형들을 파일 위쪽에 모아 두는 편이 깔끔합니다.
함수는 결국 이름 붙인 작업 묶음이고, 정의로 만들어 호출로 꺼내 씁니다. 매개변수라는 빈 자리에 인자를 넣어 동작을 바꾸고, return으로 결과 하나를 돌려받습니다. 반복되는 코드가 보이면 함수로 묶고, 함수 하나가 한 가지 일만 하도록 잘게 나누는 습관이 프로그램을 고치기 쉽게 만듭니다.