혼공학습단 C 4주차📚

하영·2024년 1월 24일
0

혼공학습단

목록 보기
10/13
post-thumbnail

기본 미션 📣

함수 유형 정리하기 | 완료

선택 미션 📣

p226 예제 7-5 |
우선 예외가 처리되지 않았다면서 stdio.h파일에 예외설정 창이 떴다.
나는 거기서 Stack overflow를 발견하였고, 직접 검색해 보았다.

오버플로우는 한정된 저장 용량을 지니고 있는데 이것을 초과하는 양의 데이터가 입력되었을 때 발생하는 현상이다.

이 뜻으로 보아 프로그램에 사용가능한 메모리를 초과하여 예제가 무한출력되지 않고 강제 종료된 것 같다.


07-1 함수의 작성과 사용

함수란 기능을 수행하는 코드 단위를 말한다. 더 나아가 printf나 scanf처럼 특정 기능을 미리 약속하고 프로그램에서 바로 사용할 수 있게 구현되어 있는 함수를 표준 라이브러리 함수라고 하는데, C언어에서는 그 이외도 필요한 함수를 만들어 사용할 수 있다.

함수를 만들려면 다음 세 가지가 중요한데, 함수 정의, 함수 호출, 함수 선언이다.

📍 함수 정의

함수를 정의해야 할 때는 다음을 생각해야 한다.
함수명, 매개변수, 반환형. 이것들이 모여 함수 원형을 이루게 되는데,

반환형 함수명(매개변수1, 매개변수2)
{
	// 함수가 수행하는 명령. 결괏값을 돌려보냄
}

여기에 수행할 내용을 넣으면 함수가 완성된다. 이렇게 함수를 만드는 것을 함수 정의라고 한다.

함수를 만들 때는 가장 먼저 함수 원형을 작성하는데, 이것들을 유의하며 작성해야 한다.
함수명은 함수의 기능을 충분히 예상할 수 있도록 적절한 단어로 식별자 사용 규칙에 따라 만든다. 또, 매개변수는 함수가 처리할 데이터를 저장하는 변수로 함수명 옆의 괄호 안에 선언한다. 이때 매개변수의 자료형이 같아도 콤마로 구분해서 따로 선언해야 한다.
반환형은 함수가 기능을 수행한 후 호출한 곳으로 돌려줄 값의 자료형을 적는다.


예제를 보며 함수의 대해 더 자세히 알아보겠다.

소스 코드 7-1.c

#include <stdio.h>

int sum(int x, int y);

int main(void)			// 5행
{
	int a = 10, b = 20;
	int result;

	result = sum(a, b);
	printf("result : %d\n", result);

	return 0;
}

int sum(int x, int y)			// 16행
{
	int temp;

	temp = x + y;

	return temp;
}

우선 함수는 다른 함수 안에 정의할 수 없다. 그렇기 때문에 5행과 16행처럼 main함수와는 분리되어 작성한다.

함수 원형을 사용해 작성한 함수 sum을 보면 변수 temp가 선언되어 있는데,
함수 내에서 필요한 변수가 있다면 이와 같이 별도로 선언한다. 그런데 여기서 중요한 점은 변수명의 사용 범위가 선언한 블록 내부로 제한되기 때문에 함수 내에서만 사용할 수 있다는 것이다. 즉, 다른 함수 속에서도 똑같은 변수명을 사용할 수 있지만 각 변수는 함수별로 독립된 저장 공간을 가져 서로 다른 변수로 쓰인다.

int sum(int x, int y)			
{
	int temp;

	temp = x + y;

	return temp;
}

함수가 실행되는 것을 보면 매개변수의 값을 더해서 변수 temp에 잠시 저장했다가 return문에서 저장해 두었던 값을 함수를 호출한 곳으로 반환하게 된다.

하지만 temp는 기능상 꼭 필요한 변수가 아니므로 return(x+y); 과 같이 두 수의 합을 바로 반환하는 것도 가능하다.

📍 함수 호출과 반환

함수는 얼마든지 만들 수 있지만, 만든 함수가 모두 자동으로 실행되는 것은 아니다.
함수를 사용하려면 먼저 함수 호출을 해야 한다.

함수를 호출할 때에는 이름을 사용하며 함수에 필요한 데이터를 괄호 안에 넣어 주는데,
이를 인수라고 한다.

result = sum(a, b);		// a와 b가 인수

인수는 상수나 변수를 쓸 수 있고 수식을 사용하면 수식의 결괏값이 인수로 쓰이게 된다.

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

호출된 함수의 실행이 끝나고 값을 반환할 땐 return문을 사용하는데, 컴파일러는 함수를 호출할 때 반환값을 저장할 공간을 미리 준비해 둔다. 이 공간은 컴파일러가 별도로 확보하는 공간이며 식별할 수 있는 이름이 없으므로 계속 사용할 수 없다.

📍 함수 선언

함수 선언은 컴파일러가 새로 만든 함수를 인식할 수 있도록 알리는 역할을 한다.

선언 방법은 간단한데, 함수 원형에 세미콜론( ; )을 붙이면 된다. 함수 선언은 main 함수 앞에 하며, 매개변수 이름은 생략할 수 있다.

int sum(int, int)		// 매개변수 이름을 생략한 형태

그런데 함수 정의가 있는데 함수 선언을 하는 이유는 무엇일까?
먼저 함수 선언을 main 함수 앞에 하는 것은 반환형을 미리 컴파일러에게 알리기 위해서이다. 그렇다면 함수 정의를 main 함수 앞에 하여 따로 함수 선언을 할 필요가 없게 만들면 되는데 함수 선언과 함수 정의를 둘 다 하는 이유는 무엇일까.

함수를 미리 선언하지 않으려면 항상 호출하는 함수 앞에 정의해야 한다. 그런데 이때 함수 간에 호출 관계가 엉켜 있다면 순서에 맞게 정의하는 일이 쉽지 않다. 그래서 필요한 함수를 main 함수 밑에 차례로 만들고 main 함수 앞에는 모든 함수를 선언해 함수의 종류와 원형을 한눈에 파악하고 자유롭게 호출할 수 있도록 한 것이다.


07-2 여러 가지 함수 유형

함수 원형은 반환형 함수명(매개변수)로 이루어진다고 하였다. 하지만 항상 반환형과 매개변수가 필요한 것은 아니다.

📍 매개변수가 없는 함수

예를 들어 키보드로 수를 입력하면 양수를 반환하는 함수를 만든다 할 때 호출한 함수로부터 값을 받을 필요가 없으므로 매개변수가 필요가 없다. 이럴 땐 매개변수의 자리에 void를 사용한다.

소스 코드 7-2.c

#include <stdio.h>

int get_num(void);

int main(void)
{
	int result;

	result = get_num();
	printf("반환값 : %d\n", result);
	return 0;
}

int get_num(void)
{
	int num;

	printf("양수 입력 : ");
	scanf("%d", &num);

	return num;
}

소스 코드처럼 괄호 안에 void를 넣어 매개변수가 없음을 표시하는데, 괄호만 사용해 매개변수가 없음을 나타낼 수 있다. 하지만 void를 넣어 매개변수가 없음을 명시적으로 표현하는 것이 좋다.

int get_num(void);

get_num 함수는 매개변수가 없으므로 호출할 때 인수가 없다.
하지만 그렇다고 함수 호출 시 void를 쓰거나 괄호를 제하면 안된다.

result = get_num();

void는 함수 정의나 선언에서 사용되므로 함수 호출 시에는 사용하지 않고, 괄호를 제하면 함수 호출이 아닌 함수의 이름으로 인식하므로 반드시 괄호는 사용해야 한다.

📍 반환값이 없는 함수

문자와 숫자를 인수로 받으면 문자를 숫자만큼 화면에 출력하는 함수처럼 데이터를 받아서 단지 화면에 출력하는 함수라면 반환값이 필요 없다. 이럴 땐 매개변수의 빈자리에 void를 사용했던 것처럼 반환형의 자리에 void를 사용하면 된다.

소스 코드 7-3.c

#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++)
	{
		printf("%c", ch);
	}

	return;
}

반환형이 없는 함수는 문자와 숫자를 받아야 하므로 매개변수는 있으나 반환형은 void를 사용한다.

void print_char(char ch, int count);

또한 반환값이 없으므로 return문을 사용할 때도 값 없이 단독으로 사용한다.

return;

심지어 return문이 없어도 함수의 코드를 모두 수행하면 호출한 곳으로 자동으로 돌아가
return문 자체를 생략할 수 있다.

return문은 함수 어디서든 사용될 수 있는데, 예를 들어 count의 값이 10보다 큰 경우 바로 함수를 끝내고 싶다면 다음과 같이 함수 중간에 return문을 사용해도 된다.

 ···
int i;
if (count > 10) return;

for (i = 0; i < count; i++)
 ···

또 반환형에서 중요한 점은 반환형이 void인 함수는 컴파일러가 반환값이 없다고 가정해 호출한 위치에 반환값을 저장할 공간을 준비하지 않는다.

📍 매개변수와 반환값이 모두 없는 함수

매개변수와 반환값이 모두 없는 함수도 있다. 예를 들어 일정한 문자열을 여러 번 출력하는 함수라면 매개변수롸 반환값이 모두 필요없다. 이때는 함수의 매개변수와 반환형에 모두 void를 쓴다.

소스 코드 7-4.c

#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");
}

위 코드를 보면 함수 정의에 return문이 없다. 또한 호출할 때 값을 주지 않으므로 수식의 일부가 아닌 독립된 문장으로 쓰이는 것을 볼 수 있다.

📍 재귀호출 함수

함수는 보통 다른 함수를 호출하는데 재귀호출 함수는 자기 자신을 호출한다.

소스 코드 7-5.c

#include <stdio.h>

void fruit(void);

int main(void)
{
	fruit();

	return 0;
}

void fruit(void)
{
	printf("apple\n");
	fruit();
}

여기서 중요한 점은 함수가 무한출력되지 않게 반복 고리를 끊을 수 있는 조건식을 반드시 포함해야 된다.

void fruit(int count)
{
	printf("apple\n");
	if (count == 3) return;
	fruit(count + 1);
}

그래서 위와 같이 매개변수와 if문을 사용해 호출 횟수를 원하는 만큼 조절하면 좋다.


이번에는 17행에 코드를 한 줄 추가해 보았다.

소스 코드 7-7.c

#include <stdio.h>

void fruit(int count);

int main(void)
{
	fruit(1);

	return 0;
}

void fruit(int count)
{
	printf("apple\n");
	if (count == 3) return;
	fruit(count + 1);
	printf("jam\n");			// 17행
}

코드를 보면 count가 3이 되면 return문이 실행되기 때문에 17행이 절대 실행되지 않을 것 같다. 하지만 실행결과를 보면 예상과 다르다.

실행결과

apple
apple
apple
jam
jam

까닭은 재귀호출 함수의 경우 최초 호출한 곳이 아니라 이전에 호출했던 곳으로 돌아가기 때문인데, 이 상황은 재귀호출이 수행될 때마다 함수의 복사본을 만들어 보면 쉽게 이해할 수 있다.
결국 재귀호출은 하나의 함수에서 코드를 반복 실행하는 듯하지만, 실제로는 새로운 함수를 실행하는 것과 같다. 따라서 재귀호출 함수는 경우에 따라 복잡한 반복문을 간단히 표현할 수 있으나 코드 읽기가 쉽지 않고 반복 호출되면서 메모리를 사용하므로 제한적으로 쓰는 것이 좋다.

0개의 댓글