1. 함수 구조
1-1. 함수 정의
- 우리가 원하는 함수가 무엇을 받아서 어떤 값을 반환할 지를 정하는 것을 의미한다. (받지 않을 지도 반환하지 않을 수도 있다.)
- 함수의 기능이 무엇인지 (그래서 함수명을 어떻게 할 것인지), 이를 위해서 무슨 데이터가 필요한지, 어떤 값을 반환할지 정한다.
- 즉, 함수의 이름, 함수가 받는 데이터(매개변수), 함수의 결과값(반환값) 을 정한다.
함수의 반환형 함수의 이름 (매개변수1, 매개변수2, ...)
{
우리가 실행할 코드
return (반환할 결과) // 이 때 반환할 수도 안 할 수도 있다.
}
- 함수의 반환형 함수의 이름 (매개변수1, 매개변수2, ...) : 이 부분을 주로 함수의 원형, 함수의 프로토타입이라고 부른다.
1-2. 함수 호출
- 함수를 사용하기 위해 우리가 미리 정의해놨던 함수를 실행하고 필요한 데이터를 넣어주는 행동을 말한다.
- 함수를 호출하면 메모리 영역인 스택에 "자료구조 스택"의 형태로 쌓인다. (추후 재귀함수에서 보충설명)
- 이를 콜스택이라고 부른다.
- 호출시에 함수의 정의에서 요구하는 파라미터 값을 넣어주는 걸 매우 권장한다.
- 단, 정수<>실수 사이에서는 형변환이 묵시적으로 발생한다.
- 하지만 컴파일시 -W옵션을 걸어주는 경우, 컴파일을 막아버리니 함수의 프로토타입을 존중하는 것을 다시 권장한다.
#include <stdio.h>
int sum(int x, int y); //이 부분을 "함수의 프로토타입" 이라고 부른다.
int main(void)
{
int a = 10, b = 20;
int result;
result = sum(a, b); // 이 부분이 함수의 호출이다.
printf("result : %d\n", result); // 30이 출력된다.
return (0);
}
int sum(int x, int y) // 함수의 정의가 되어있는 곳이다. 매개변수는 int x, int y 에 해당한다
{
return (x + y); //매개변수 x와 y를 이 함수를 호출할 때 받아서 두 값을 더해서 결과를 반환한다.
}
// 형변환 예시
#include <stdio.h>
int sum(double x, double y); //double 형으로 수정한다
int main(void)
{
int a = 10, b = 20;
int result;
result = sum(a, b); //double의 자리에 int 형 변수를 넣었다.
printf("result : %d\n", result); //하지만 동일하게 30이 출력된다.
return (0);
}
int sum(double x, double y)
{
return (x + y);
}
1-3. 함수 선언
- 컴파일러가 우리가 만든 함수를 인식할 수 있도록 알리기 위해 헤더파일이나 프로그램 상단에 미리 작성하는 것을 말한다.
- 함수 선언에서는 프로토타입과 세미콜론을 붙이면 된다.
- 프로그램의 규모가 커지면 헤더파일에서 함수를 선언하는 용도로 사용하거나, 여러가지 함수를 위에서 정의하고 사용해버리면 우리의 main함수가 너무 밑에 깔려 보기 힘들어지는 경우가 있어서 미리 선언하는 경우가 있다.
- 위 예시 코드에서는 가장 상단의 int sum(int x, int y); 이 부분에 해당한다.
- 즉 우리가 함수를 사용하기 위해서는 호출하는 부분보다 상단에 이미 정의나 선언되어 있거나 함수가 선언되어 있는 헤더파일을 include 해야한다.
#include <stdio.h>
int sum(int x, int y); //이 부분이다! 만약 이 부분이 없으면
//main 안에 있는 sum함수를 컴파일러는 인식하지 못한다.
int main(void)
{
int a = 10, b = 20;
int result;
result = sum(a, b);
printf("result : %d\n", result);
return (0);
}
int sum(int x, int y)
{
return (x + y);
}
2. 함수 형태
- 우리가 원하는 함수가 무엇을 받아서 어떤 값을 반환할 지를 정하는 것을 의미한다. (받지 않을 지도 반환하지 않을 수도 있다.)
2-1. 매개변수와 반환에 void
- 반환형은 존재해도 매개변수가 없는 경우, 반환형은 없고 매개변수는 존재하는 경우, 둘 다 없는 경우가 존재한다.
- 선언시에는 void를 넣거나 없을 수 도 있지만 호출에는 ()안에 아무것도 작성해서는 안된다.
- 또 void로 아무것도 반환하지 않는 함수일 지라도 return ; 을 사용해서 함수 진행 도중에 종료할 수 있다.
- 예시코드는 반환형과 매개변수 둘 다 존재하지 않는 경우에 해당한다.
#include <stdio.h>
void print_line(void); // 선언시에는 매개변수가 없음을 명시적으로 void를 작성하여 알려준다.
int main(void)
{
print_line(); // 호출시에는 ()로 void를 넣지 않는다.
printf("something....\n");
print_line();
}
void print_line(void) // 단순 -를 50번 출력하는 역할만 할 뿐 값을 반환하지 않는다.
{
for (int i = 0; i < 50; i++)
printf("-");
printf("\n");
}
2-2. 재귀함수 이해
- 함수 안에 자기 스스로를 다시 호출하는 함수를 말한다.
- 아래가 재귀함수의 간단한 예시이며 fruit을 호출하면 apple이 출력되고 다시 fruit이 호출되고 그럼 다시 apple이 출력되고....
- 이론상 apple이 계속 출력 되어야 하지만 실제로는 그렇지 못한다.
- 함수는 호출되면 (코드/데이터/스택/힙) 으로 구성 되어 있는 메모리 중 스택에 메모리가 할당 된다.
- 이 메모리는 무한한 것이 아니라 우리 컴퓨터 상에 하드웨어로 존재하는 메모리이다.
- 간단히 말하면 메모리를 4가지 영역으로 나눠져 있는데, 스택에 계속 메모리가 쌓이다 보면 다른 영역을 침범하게 된다. (이 경우는 힙 영역)
- 이 경우를 stackoverflow, 스택 오버플로우라고 부르며 오류를 뱉으면서 프로세스가 종료된다.
- 재귀 함수의 경우 함수가 복제되서 다시 스택에 쌓이는 것으로 동작하는데, 자기가 호출한 함수가 종료되기 전까지 콜스택에서 제거 되지 않는다.
- 즉 무한히 스택에 쌓이다 보면 다른 메모리영역을 침범하게 된다.
#include <stdio.h>
void fruit(void);
int main(void)
{
fruit();
return (0);
}
void fruit(void)
{
printf("apple\n");
fruit();
}
fruit() 첫번째에서 호출한 fruit() |
fruit() main문 안 첫번째 함수 |
... 계속해서 호출 됨 (다른 메모리 침범) |
fruit() 첫번째에서 호출한 fruit() |
fruit() main문 안 첫번째 함수 |
- 또한 재귀 함수에서 return 이란 원래 함수의 종료를 말하지 않는다.
- 함수 자체가 복제되어서 다시 스택영역에 메모리를 할당되고 실행되는 것이므로 return 을 선언한 "그 함수만" 종류하게 된다.
- 아래 코드를 보면 apple은 3번 (cnt가 0, 1, 2) jam은 2번 (cnt가 2일 때 return 해서 3번째 호출된 함수만 종료된다.) 출력됨을 알 수 있다.
#include <stdio.h>
void fruit(int cnt);
int main(void)
{
fruit(0);
return (0);
}
void fruit(int cnt)
{
printf("apple\n");
if (cnt == 2)
return ;
fruit(cnt + 1);
printf("jam\n");
}
3. 도전 실전 예제
#include <stdio.h>
int rec_func(int n);
int main(void)
{
printf("%d", rec_func(10));
}
int rec_func(int n)
{
int res;
if (n == 1)
return 1;
res = (rec_func(n - 1) + n);
return (res);
}