기본 미션 : 함수의 유형을 정리하여 공유하기
선택 미션 : p. 198 7-5. 직접 해보는 손코딩 예제 테스트 후 apple 출력하고 종료한 화면 캡쳐하기 왜 무한대로 출력되지 않는지 이유에 대해 생각하고 정리해보기
방학식을 했지만 방학이 아닙니다...! ㅜ
아직까지도 기숙사에서 평소와 다름 없이 학교로 등교를 하고 7교시까지 끝내고 오기 때문이죠
그래서 그런지 2주차 3주차보다 블로그 글 쓰는 기간이 길어졌네요ㅜㅠ
MOS자격증을 현재 준비 중에 있기 때문에 그만큼 마음이 나태진 게 아닌가하는 생각도 듭니다.
지금은 MOS 엑셀 합격해서 매우 기뻐요!!!!!!!!!!!!!!!!
다시 초심찾고 열심히 미션을 수행해보겠습니다..!
다음 주 혼공단 방학이다!!!!!
미션은 함수의 유형만을 정리하여 공유하기로 되어 있지만 아까우니까 이번 기회에 함수에 대해서 다시 정확히 짚고 넘어가고 싶어서 함수의 정의부터 차근히 정리하는 시간을 가져보도록 하겠습니다.
: 기능을 수행하는 코드 단위
지금껏 사용해왔던 main함수
가 대표적인 함수입니다.
그리고 printf
scanf
도 함수인데 이처럼 특정 기능을 미리 약속하고 프로그램에서 바로 사용할 수 있게 구현되어 있는 함수를 표준 라이브러리 함수
라고 합니다. 표준 라이브러리 함수
는 stdio.h 헤더 파일
에 포함되어 있습니다.
이처럼 C언어에서 표준으로 제공하는 함수 외에도 자주 사용하는 코드를 함수로 만들어 필요할 때 사용할 수도 있습니다. 함수를 만들려면 3가지가 중요한데 바로 다음 내용입니다.
: 함수를 만드는 것
이제껏 main 함수
만 사용하여 프로그램을 만드셨다면 이제부터는 새로운 함수를 만들어봅시다.
새로운 함수를 만드는 방법도 main 함수
를 만드는 방법과 크게 다르지 않습니다. 다만 함수를 만들기 전에 다음 3가지를 꼭 점검해야 합니다.
1. 함수명
2. 매개변수
3. 반환형
간단하게 답한 이 질문이 바로 함수 원형 (function prototype)
을 이룹니다. 이 내용을 코드 형식으로 쓰면 다음과 같습니다.
함수 정의
반환형 함수명 ( 매개변수1, 매개변수2 ) // 함수 원형
{
// 함수가 수행하는 명령, 결괏값( 매개변수1+매개변수2 )을 돌려보냄
}
ex)
int sum(int x, int y) // sum 함수 정의 시작 ( 함수 원형 )
{
int temp; // 두 정수의 합을 잠시 저장할 변수
temp = x + y; // x와 y의 합을 temp에 저장
return temp; // temp의 값을 반환
} // sum 함수의 끝
*함수 안에서만 사용하는 변수명은 다른 함수의 변수명과 같을 수 있다.
우리는 함수를 얼마든지 만들 수 있지만 만든 함수가 모두 자동으로 실행되는 것은 아닙니다. 함수를 사용하기 위해서는 반드시 함수 호출을 해야합니다. 함수는 호출을 통해 실행되기 때문입니다.
result = sum(a, b); // sum 함수 호출
위와 같이 함수를 호출할 때는 함수에 필요한 데이터를 괄호 안에 넣어주는데 이를 인수 argument
라고 합니다. sum함수
를 호출해서 a+b
를 실행하려면 함수명과 함계 괄호 안에 a,b
를 넣고 호출합니다. 인수는 상수나 변수를 쓸 수 있고 수식을 사용하면 수식의 결괏값이 인수로 쓰입니다.
sum(30, 40)
sum(a*2, b/3)
이같이 함수를 호출할 때 이렇게 입력된 인수 a,b
의 값은 호출된 함수의 매개변수 parameter
에 복사되어 사용됩니다.
위에서 알 수 있듯이 sum함수
가 호출되면 main함수
의 실행은 잠시 멈추고 sum함수
의 부분에 있는 코드가 실행됩니다.
호출된 함수가 실행을 끝내고 값을 반환할 때 return문
을 사용합니다.
위의 사진을 봅시다. main함수
를 사용하는 도중에 sum함수
를 사용하여 오른쪽의 코드로 넘어갑니다. sum함수
에서는 x + y(a+b)
의 값을 temp
에 저장한 후 return함수
를 이용하여 result
에 temp
의 값을 반환합니다. 잘 와닿지 않으신 분들은 sum(a, b)
의 자리에 temp
이 들어갔다고 보시면 이해가 쉬울 것입니다.
또 temp
와 같은 함수의 반환값은 수식의 일부로 사용할 수 있습니다.
ex)
result = sum(a, b) * 2;
3.대입 1.반환값 2.곱셈
sum함수
의 반환값에 2를 곱한 후 result
에 그 결과를 대입할 수 있습니다.
함수 선언은 컴파일러가 새로 만든 함수를 인식할 수 있도록 알리는 역할을 합니다.
선언 방법
int sum (int x, int y);
반환형 함수명 매개변수
함수 원형에 세미콜론;
을 붙인 형태입니다.
여기서 매개변수 이름은 생략할 수 있습니다.
int sum(int, int);
그런데 여기까지 온전히 이해를 하신 분이라면 의문을 가지실 겁니다.
" 함수 정의와 함수 선언은 {}
;
의 차이뿐인 것 같은데 굳이 함수 선언이 필요한가요? "
네! 필요합니다. 그렇다면 왜 필요할까요?
함수 정의의 필요성
함수 선언에서 반환값의 형태를 확인합니다.
*프로그램은 sum함수
가 먼저 있어도 항상 main함수
부터 시작한다.
함수의 호출 형식에 문제가 없는지 검사합니다.
int
일 경우 함수 정의나 함수 선언없이 함수를 호출할 수 있음함수 선언
을 하면 함수를 만들지 않고도 함수의 형태를 미리 알릴 수 있다.함수 정의
는 원하는 기능의 함수를 직접 만드는 것이다.함수 호출
은 만든 함수를 사용할 때 사용한다.return
은 함수를 실행한 다음 값을 반환할 때 사용하는 제어문이다.함수의 3가지 상태
구분 | 예 | 설명 |
---|---|---|
함수 선언 | int sum(int a, int b) ; | 함수의 형태를 알린다. 함수 원형에 세미콜론을 붙인다. |
함수 정의 | int sum(int a, int b) { return a + b ; } | 함수를 만든다. 반환값의 형태, 이름, 매개변수를 표시하고 블록 안에 기능을 구현한다. |
함수 호출 | sum(10, 20) ; | 함수를 사용한다. 함수에 필요한 값을 인수로 준다. |
함수의 원형은 반환형 함수명(매개변수)
라고 배웠습니다.
하지만 언제나 함수에 반환형과 매개변수가 아닙니다. 즉 매개변수나 반환형이 없는 함수도 있다는 것입니다.
이번 파트에서는 이러한 함수들의 종류를 알아볼겁니다.
우리가 만약 "키보드로 수를 입력하면 양수를 반환하는 함수" 를 만든다면 호출한 함수로부터 값을 받을 필요가 없겠죠? 물론 값을 받을 필요가 없으니 매개변수도 필요없을 것입니다. 이 때 함수의 매개변수 자리에는 void
를 사용합니다.
#include <stdio.h>
int get_num(void); //함수 선언
int main(void)
{
int result;
result = get_num(); //함수 호출, 반환값은 result에 저장
printf("반환값 : %d\n", result); //반환값 출력
return 0;
}
int get_num(void) //매개변수가 없고 반환형만 있다.
{
int num; //키보드 입력값을 저장할 변수
printf("양수 입력 : "); //입력 안내 메시지
scanf_s("%d", &num); //키보드 입력
return num; //입력한 값 반환
}
실행결과
양수 입력 : 10
반환값 : 10
+음수 입력에 대응해봅시다.
#include <stdio.h>
int get_num(void); //함수 선언
int main(void)
{
int result;
result = get_num(); //함수 호출, 반환값은 result에 저장
printf("반환값 : %d\n", result); //반환값 출력
return 0;
}
int get_num(void) //매개변수가 없고 반환형만 있다.
{
int num; //키보드 입력값을 저장할 변수
while (num < 0)
{
printf("양수를 입력하세요!\n");
printf("양수 입력 : "); //입력 안내 메시지
scanf_s("%d", &num); //키보드 입력
}
return num; //입력한 값 반환
}
함수는 기능에 따라 형태가 결정되는데 데이터를 받아서 단지 화면에 출력하는 함수라면 특별히 반환값이 필요 없습니다.
만약 우리가 문자와 숫자를 인수로 받는다면, 화면에 출력한 내용이 함수가 수행한 결과이므로 호출한 곳으로 특별히 값을 반환할 필요가 없습니다. 이전의 매개변수가 없는 함수
에서 void
를 사용했던 것처럼 이 때는 반환형의 자리에 void
를 사용합니다.
반환값이 없는 함수
#include <stdio.h>
void print_char(char ch, int count); //함수 선언
int main(void)
{
print_char('@', 5); //문자와 숫자를 주고 함수 호출
return 0;
}
void print_char(char ch, int count) //매개변수는 있으나 반환형은 없다.
{
int i;
for (i = 0; i < count; i++) // i는 0부터 count-1까지 증가, count번 반복
{
printf("%c", ch); //매개변수 ch에 받은 문자 출력
}
return;
}
실행결과
@@@@@
void
를 사용합니다.return문
은 값 없이 단독으로 사용합니다. 심지어는 return문
자체를 생략하는 것도 가능합니다.return문
을 사용하여 함수의 실행을 종료할 수 있습니다.만약 일정한 문자열을 여러 번 출력하는 함수가 있다면 어떨까요?
문자열을 출력하기 때문에 매개변수를 가질 필요가 없을 것입니다.
또한 여러 번 출력하는 것이 전부이기 때문에 반환값 또한 필요가 없습니다.
이 때에는 함수의 매개변수와 반환형에 모두 void
를 씁니다.
반환값과 매개변수가 모두 없는 함수
#include <stdio.h>
void print_line(void); //함수 선언
int main(void)
{
print_line(); //함수 선언
printf("학번 이름 전공 학점\n");
print_line(); //함수 호출
return 0;
}
void print_line(void)
{
int i;
for (i = 0; i < 50; i++)
{
printf("-");
}
printf("\n");
}
실행결과
------------------------------------------------
학번 이름 전공 학점
------------------------------------------------
일반 함수는 보통 다른 함수를 호출하는데 재귀호출 함수 recursive call funtion
는 자기 자신을 호출합니다. 함수 안에서 자신을 호출하면 재귀호출 함수입니다.
재귀호출 함수
#include <stdio.h>
void fruit(void); //함수 선언
int main(void)
{
fruit(); //함수 호출
return 0;
}
void fruit(void)
{
printf("apple\n");
fruit(); //자신을 다시 호출
}
실행결과
apple
apple
apple
apple
apple
...
7행 부분에 함수를 호출하고 12행 부분에서 재귀호출 함수를 정의하고 있습니다. 함수가 apple
을 출력하라는 명령을 수행한 후에는 자동으로 반환하는데 반환 전에 자신을 호출하므로 무한으로 apple
이 출력되는 것입니다.
이렇게 무한으로 출력하다 종료가 되는데 이는 프로그램 하나가 쓸 수 있는 메모리를 모두 사용하였기 때문에 강제 종료가 되는 것입니다.
이처럼 중간에 종료되는 것은 정상적인 종료가 아닙니다. 따라서 정상적으로 종료하기 위해서 재귀호출 함수는 반복 고리를 끊을 수 있는 조건식을 반드시 포함하여야 합니다.
3번 실행되는 재귀호출 함수
#include <stdio.h>
void fruit(int count);
int main(void)
{
fruit(1); //처음 호출하므로 1을 인수로 준다.
return 0;게
}
void fruit(int count) //호출 횟수를 매개변수에 저장
{
printf("apple\n");
if (count == 3) return; //호출 횟수가 3이면 반환하고 끝낸다.
fruit(count + 1); //재호출할 때 호출 횟수를 1 증가
}
실행결과
apple
apple
apple
fruit함수
에 인수를 1
로 줍니다.count
에 1
이 저장됩니다.fruit함수
로 가서 apple
을 출력한 후 count++
을 합니다.count
의 값이 3
이 되면 return
으로 반환한 후 끝냅니다.이렇게 매개변수와 if문을 사용하면 호출 횟수를 원하는 만큼 조절할 수 있습니다.
이제 또 하나의 의구심이 들지 않습니까?
"재귀호출보다는 익숙한 반복문을 사용해도 되지 않을까?"
이번 파트에서는 재귀호출과 반복문의 차이점을 살펴보겠습니다.
아까 보았던 재귀호출의 코드에 한 줄만 추가한 모습입니다.
#include <stdio.h>
void fruit(int count);
int main(void)
{
fruit(1); //처음 호출하므로 1을 인수로 준다.
return 0;
}
void fruit(int count) //호출 횟수를 매개변수에 저장
{
printf("apple\n");
if (count == 3) return; //호출 횟수가 3이면 반환하고 끝낸다.
fruit(count + 1); //재호출할 때 호출 횟수를 1 증가
printf("jam\n");
}
실행결과
apple
apple
apple
jam
jam
실행결과까지 보니 무언가 이상하지 않나요?
count
가 3
이 되면 반환되어 끝날 것 같은데 jam
이 두 번 더 출력이 되었습니다. 왜 그럴까요?
재귀호출 함수는 최초 호출한 곳이 아니라 이전에 출력했던 곳으로 돌아갑니다. 즉 재귀호출 함수는 직전에 호출한 곳으로 돌아간다는 것입니다. 이 상황은 재귀호출이 수행될 때마다 함수의 복사본을 만들어보면 쉽게 이해할 수 있습니다.
[사진]
결국 재귀호출은 하나의 함수에서 코드를 반복 실행하는 듯하지만 실제로는 새로운 함수를 실행하는 것과 같습니다. 이러한 점이 반복문과 재귀호출의 차이점인 것이죠.
재귀호출 함수는 경우에 따라 복잡한 반복문을 간단히 표현할 수 있으나 코드 읽기가 쉽지 않고 반복 호출되면서 메모리를 사용하므로 제한적으로 쓰는 것이 좋습니다.
매개변수가 없어도 된다.
반환형을 쓰지 않아도 된다.
매개변수와 반환값을 둘 다 쓰지 않아도 된다.
void
를 적어준다.재귀호출 함수
는 자기 자신을 다시 호출한다.다양한 함수 형태
형태 | 구분 | 설명 |
---|---|---|
매개변수가 없는 경우 | 선언 | in get_num(void) 또는 get_num(); |
특징 | 호출할 때 인수 없이 괄호만 사용한다. | |
반환형이 없는 경우 | 선언 | void print_char(char ch, int count); |
특징 | 반환할 때 return문 을 쓰지 않거나 return문 만 사용한다.호출 문장을 수식의 일부로 쓸 수 없다. | |
반환형이 매개변수와 모두 없는 경우 | 선언 | void print_title(void); |
특징 | 두 가지 경우의 특징을 모두 포함한다. |
재귀호출 함수
형태 | 구분 | 설명 |
---|---|---|
재귀호출 함수 | 선언 | void fruit() { ... fruit ( ) ; ... } |
특징 | 함수 안에 재귀호출을 멈추는 조건이 있어야 한다. |
함수는 호출만으로도 일정 크기의 메모리를 사용합니다. 그런데 무한으로 호출하게 되면 프로그램 하나가 쓸 수 있는 메모리, 즉 해당 프로세스에 할당된 스택 메모리)를 모두 사용하기 때문에 강제로 프로그램이 종료가 되는 것입니다.
이번 챕터에서는 의외로 모르는 것들이 많이 나왔습니다. 또한 헷갈리는 부분도 상당수였습니다. 함수의 세 가지 형태를 구분하는 것, 함수의 여러 종류, 재귀호출과 반복문의 차이점을 아는 것이 꽤나 어려웠지만 한 번만에 이해할 수는 없는 것이잖아요? 앞으로 계속 여러 형태의 함수를 접하면서 속히 제 지식으로 만들고 싶어집니다!