[C] 혼공단_4주차_Chapter 07

Haeun Noh·2022년 7월 30일
0

0728


4주차 미션

기본 미션 : 함수의 유형을 정리하여 공유하기

선택 미션 : p. 198 7-5. 직접 해보는 손코딩 예제 테스트 후 apple 출력하고 종료한 화면 캡쳐하기 왜 무한대로 출력되지 않는지 이유에 대해 생각하고 정리해보기

방학식을 했지만 방학이 아닙니다...! ㅜ
아직까지도 기숙사에서 평소와 다름 없이 학교로 등교를 하고 7교시까지 끝내고 오기 때문이죠
그래서 그런지 2주차 3주차보다 블로그 글 쓰는 기간이 길어졌네요ㅜㅠ
MOS자격증을 현재 준비 중에 있기 때문에 그만큼 마음이 나태진 게 아닌가하는 생각도 듭니다.
지금은 MOS 엑셀 합격해서 매우 기뻐요!!!!!!!!!!!!!!!!

다시 초심찾고 열심히 미션을 수행해보겠습니다..!
다음 주 혼공단 방학이다!!!!!



함수의 유형을 정리하여 공유하기

미션은 함수의 유형만을 정리하여 공유하기로 되어 있지만 아까우니까 이번 기회에 함수에 대해서 다시 정확히 짚고 넘어가고 싶어서 함수의 정의부터 차근히 정리하는 시간을 가져보도록 하겠습니다.


함수란?

: 기능을 수행하는 코드 단위

지금껏 사용해왔던 main함수가 대표적인 함수입니다.

그리고 printf scanf도 함수인데 이처럼 특정 기능을 미리 약속하고 프로그램에서 바로 사용할 수 있게 구현되어 있는 함수를 표준 라이브러리 함수라고 합니다. 표준 라이브러리 함수stdio.h 헤더 파일에 포함되어 있습니다.


이처럼 C언어에서 표준으로 제공하는 함수 외에도 자주 사용하는 코드를 함수로 만들어 필요할 때 사용할 수도 있습니다. 함수를 만들려면 3가지가 중요한데 바로 다음 내용입니다.

  • 함수 정의 : 함수를 실제 코드로 만드는 것이며 기능을 구현합니다.
  • 함수 호출 : 함수 호출을 해야지 함수를 사용할 수 있습니다.
  • 함수 선언 : 프로그램의 상단에서 어떤 함수를 사용할 것이라고 컴파일러에 정보를 주는 역할을 합니다.

함수 정의

: 함수를 만드는 것

이제껏 main 함수만 사용하여 프로그램을 만드셨다면 이제부터는 새로운 함수를 만들어봅시다.

새로운 함수를 만드는 방법도 main 함수를 만드는 방법과 크게 다르지 않습니다. 다만 함수를 만들기 전에 다음 3가지를 꼭 점검해야 합니다.

  1. 함수의 기능에 맞는 이름인가? - 1. 함수명
  2. 함수가 기능을 수행하는 데 필요한 데이터는 무엇인가? - 2. 매개변수
  3. 함수가 수행된 후의 결과는 무엇인가? - 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 : 함수명
  • (30, 40) : 상수 인수
sum(a*2, b/3)
  • sum : 함수명
  • ( a * 2, b / 3 ) : 수식 인수 (수식의 결괏값이 인수로 쓰임)

이같이 함수를 호출할 때 이렇게 입력된 인수 a,b의 값은 호출된 함수의 매개변수 parameter에 복사되어 사용됩니다.

위에서 알 수 있듯이 sum함수가 호출되면 main함수의 실행은 잠시 멈추고 sum함수의 부분에 있는 코드가 실행됩니다.


함수 반환

호출된 함수가 실행을 끝내고 값을 반환할 때 return문을 사용합니다.

위의 사진을 봅시다. main함수를 사용하는 도중에 sum함수를 사용하여 오른쪽의 코드로 넘어갑니다. sum함수에서는 x + y(a+b)의 값을 temp에 저장한 후 return함수를 이용하여 resulttemp의 값을 반환합니다. 잘 와닿지 않으신 분들은 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);

그런데 여기까지 온전히 이해를 하신 분이라면 의문을 가지실 겁니다.

" 함수 정의와 함수 선언은 {} ;의 차이뿐인 것 같은데 굳이 함수 선언이 필요한가요? "
네! 필요합니다. 그렇다면 왜 필요할까요?


함수 정의의 필요성

  1. 함수 선언에서 반환값의 형태를 확인합니다.

    • 컴파일러에게 함수 호출 전에 선언을 통해 미리 반환형을 알릴 필요가 있음
    • 함수 정의에서도 반환형을 확인할 수 있으므로 함수 호출 이전에 함수를 정의하는 방법도 있음
    • 함수 선언이 없으면 함수 정의는 항상 함수 호출 이전에 있어야 함

    *프로그램은 sum함수가 먼저 있어도 항상 main함수부터 시작한다.

  2. 함수의 호출 형식에 문제가 없는지 검사합니다.

    • 함수 선언문에는 매개변수의 개수와 형태에 대한 정보가 있기 때문에 호출할 때 정확한 값을 주는지 검사할 수 있음
    • 정의된 함수의 반환형이 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
  1. fruit함수에 인수를 1로 줍니다.
  2. count1이 저장됩니다.
  3. fruit함수로 가서 apple을 출력한 후 count++을 합니다.
  4. 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

실행결과까지 보니 무언가 이상하지 않나요?
count3이 되면 반환되어 끝날 것 같은데 jam이 두 번 더 출력이 되었습니다. 왜 그럴까요?

재귀호출 함수는 최초 호출한 곳이 아니라 이전에 출력했던 곳으로 돌아갑니다. 즉 재귀호출 함수는 직전에 호출한 곳으로 돌아간다는 것입니다. 이 상황은 재귀호출이 수행될 때마다 함수의 복사본을 만들어보면 쉽게 이해할 수 있습니다.

[사진]

결국 재귀호출은 하나의 함수에서 코드를 반복 실행하는 듯하지만 실제로는 새로운 함수를 실행하는 것과 같습니다. 이러한 점이 반복문과 재귀호출의 차이점인 것이죠.

재귀호출 함수는 경우에 따라 복잡한 반복문을 간단히 표현할 수 있으나 코드 읽기가 쉽지 않고 반복 호출되면서 메모리를 사용하므로 제한적으로 쓰는 것이 좋습니다.


마무리 정리

  • 처리할 데이터를 스스로 입력하는 함수에는 매개변수가 없어도 된다.
  • 전달받은 데이터를 화면에 출력하는 함수는 반환형을 쓰지 않아도 된다.
  • 같은 내용을 단지 화면에 출력하는 함수는 매개변수와 반환값을 둘 다 쓰지 않아도 된다.
  • 매개변수와 반환값이 없을 때 빈 공간은 void를 적어준다.
  • 재귀호출 함수는 자기 자신을 다시 호출한다.

다양한 함수 형태

형태구분설명
매개변수가 없는 경우선언in get_num(void) 또는 get_num();
특징호출할 때 인수 없이 괄호만 사용한다.
반환형이 없는 경우선언void print_char(char ch, int count);
특징반환할 때 return문을 쓰지 않거나 return문만 사용한다.
호출 문장을 수식의 일부로 쓸 수 없다.
반환형이 매개변수와 모두 없는 경우선언void print_title(void);
특징두 가지 경우의 특징을 모두 포함한다.

재귀호출 함수

형태구분설명
재귀호출 함수선언void fruit() { ... fruit ( ) ; ... }
특징함수 안에 재귀호출을 멈추는 조건이 있어야 한다.


p. 198 7-5. 직접 해보는 손코딩 예제 테스트 후 apple 출력하고 종료한 화면 캡쳐하기 왜 무한대로 출력되지 않는지 이유에 대해 생각하고 정리해보기

무한대로 출력이 안 되는 이유

함수는 호출만으로도 일정 크기의 메모리를 사용합니다. 그런데 무한으로 호출하게 되면 프로그램 하나가 쓸 수 있는 메모리, 즉 해당 프로세스에 할당된 스택 메모리)를 모두 사용하기 때문에 강제로 프로그램이 종료가 되는 것입니다.



마무리하며..

이번 챕터에서는 의외로 모르는 것들이 많이 나왔습니다. 또한 헷갈리는 부분도 상당수였습니다. 함수의 세 가지 형태를 구분하는 것, 함수의 여러 종류, 재귀호출과 반복문의 차이점을 아는 것이 꽤나 어려웠지만 한 번만에 이해할 수는 없는 것이잖아요? 앞으로 계속 여러 형태의 함수를 접하면서 속히 제 지식으로 만들고 싶어집니다!



profile
기록의 힘을 믿는 개발자, 노하은입니다!

0개의 댓글