C언어에 있어 매우 중요한 부분인 함수에 대해 알아보겠다.
함수는 기능을 제공한다. 왜 함수를 만드는가 하면 기능을 나누어 실행하기 위해서라고 생각할 수 있다.
예를 들어 리스트에서 키값을 찾아 삭제, 검색을 할 수 있는 프로그램을 만들 때, 키값을 리스트의 값들과 비교하여 일치하면 반환하는 함수, 그 값을 삭제하는 함수, 검색 결과를 리턴하는 함수로 기능을 나누어 작성해 하나의 프로그램을 만들 수 있다.
기능 단위로 쪼개면 여러 이점이 있는데, 큰 문제를 작게 나누어 차근차근 해결할 수 있고, 기능을 반복할 때 함수를 여러 번 호출할 수 있으며 함수 호출 시 전달할 인자를 바꿀 수 있으며, 코드 수정도 간편해지고 여러 명이 같이 작업할 때도 용이하다.
함수의 기본 구조는 다음과 같다.
반환형태 name (입력형태){
}
C언어 함수에는 전달인자나 반환값이 없는 경우도 존재한다.
👉🏻printf함수는 전달인자가 있으며 반환값도 존재하는 함수이다. printf 함수의 반환값은 문자열의 길이를 반환한다.
4가지 케이스를 모두 살펴보겠다.
가장 일반적인 형식의 함수이다.
두 정수를 인자로 받아 더 큰 수를 반환하는 함수를 예시로 작성해보았다.
int Greater(int num1, int num2){
if(num1 > num2)
return num1;
else
return num2;
}
함수를 정의할 때에는 반환형과 매개변수의 선언이 필요하다. 반환형은 int, 두 매개변수는 int로 선언하였다. 매개변수란 함수호출 시 전달되는 인자를 받아 저장할 함수에서의 변수를 말한다.
함수호출 시 전달할 수 있는 인자의 수는 여러개 가능하지만 반환하는 값은 하나만 가능하다.
함수의 호출문은 반환값이 된다.
한번 정의한 함수는 몇 번이고 사용할 수 있다.
함수 내에서 함수를 호출할 수 있다.
반환값이 없다면 void로 두고 return하지 않으면 된다.
void ShowResult(int num1, int num2){
if(num1 > num2)
printf("%d", num1);
else
printf("%d", num2);
}
더 큰수만 출력하면 되고 값을 저장할 필요가 없어 리턴하지 않는 함수를 만들었다.
인자를 전달하지 않는 경우 매개변수를 void로 둔다.
int main(void){
return 0;
}
우리가 사용해오던 main함수는 값을 리턴하지만 인자는 받지 않았다.
이러한 함수는 호출 시 인자를 전달하면 안된다.
인자도 전달하지 않고 반환값도 없다.
void ShowString(void){
printf("문자열 출력");
}
키워드 return은 두가지 기능을 한다.
📍함수를 빠져나감
📍값을 리턴
반환형이 void인 함수도 함수를 빠져나가는 의미로 return문을 사용할 수 있다. 값을 리턴하지 않고 사용하면 된다.
❗함수는 먼저 선언을 해야 사용할 수 있다. 함수 작성문을 실행문 이전에 두고 싶다면, 앞쪽에 미리 선언해두고 뒤에 작성하는 방법이 있다. 미리 선언 시에는 매개변수의 이름을 생략하고 데이터 타입만 기재할 수 있다.
ex)
void ShowResult(int, int);
int main(void){
ShowResult(5, 10);
return 0;
}
void ShowResult(int num1, int num2){
if(num1 > num2)
printf("%d", num1);
else
printf("%d", num2);
}
함수의 선언문을 코드 앞 쪽에 모아놓고 프로그램을 작성하기도 한다.
지역변수에서 지역이란 중괄호로 감싸진 부분을 말한다. 중괄호 내에 선언된 변수들은 지역변수이다. 지역변수는 선언된 지역 내에서만 접근가능하며 유효하다. 한 지역 내 동일한 이름의 변수를 선언할 수 없다. (다른 지역에 동일한 이름의 변수 선언 가능)
함수 내의 변수는 함수가 호출될 때마다 새롭게 할당되고 함수의 실행이 종료되면 소멸된다.
이러한 지역변수는 함수에만 해당하는 것이 아니라, 반복문, 조건문 등 중괄호로 묶인 범위 안에 해당된다.
👉🏻함수 정의 시 선언하는 매개변수 또한 지역변수라 할 수 있다. 따라서 함수 내에서만 사용 가능하며 함수 반환 시 소멸된다.
전역변수는 중괄호 내에 선언되지 않는 변수이며 전체 영역 어디서든 접근 가능하다. 프로그램이 시작하는 동시에 메모리 공간에 할당되어 종료할 때까지 유지된다. 초기화 없이 전역변수를 선언하면 기본적으로 0으로 초기화된다.
전역변수와 동일한 이름의 지역변수가 선언된다면, 그 지역 내에서는 지역변수가 사용되고, 그 지역 밖에서는 전역변수가 사용된다.
하지만 이름을 겹치게 사용하지 않는 것이 좋으며, 전역변수 또한 코드의 복잡도에 영향을 미치므로 필요한 경우가 아니면 되도록 피하는 것이 좋다.
지역변수나 전역변수에 static 선언을 붙여서 static 변수를 만든다.
지역변수에 static을 붙이면 선언된 함수 내에서만 접근 가능하지만 프로그램 시작부터 종료 시까지 메모리 공간에 존재하게 된다. 처음 한 번만 초기화되고 초기화하지 않으면 0이 저장된다.
따라서 함수 내 static 변수가 선언되면 함수가 몇 번 호출되어도 계속 새로 할당되고 소멸되는 과정을 겪지 않는다.
즉, 전역변수와 같은 성질을 가지면서 접근 범위는 중괄호 내로 제한하는 것이다. static변수 선언문은 실제로는 함수 실행 시에 실행되는 것이 아니라 프로그램 시작과 동시에 실행되고 함수 내에는 그 선언문이 없다고 볼 수 있다.
지역변수에 register 선언을 추가해 register 변수를 만든다. register 변수는 CPU내의 저장공간인 레지스터에 값을 저장하겠다는 의미이다. 하지만 register 변수를 선언해도 컴파일러에 의해 반려되어 레지스터에 저장되지 않을 수 있다.
재귀함수는 함수 내에서 자기 자신을 호출하는 함수를 말한다. 다른 함수를 호출하는 것은 해당하지 않는다.
아래의 예제를 보자.
#include <stdio.h>
void Recursive(int);
int main(void) {
Recursive(5);
return 0;
}
void Recursive(int num) {
if (num <= 0)
return;
printf("Recursive call %d\n", num);
Recursive(--num);
}
Recursive call 5
Recursive call 4
Recursive call 3
Recursive call 2
Recursive call 1
여기서 Recursive 함수의 print문의 위치를 바꿔보면 구조를 이해하는 데 도움이 될 것이다.
#include <stdio.h>
void Recursive(int);
int main(void) {
Recursive(5);
return 0;
}
void Recursive(int num) {
if (num <= 0)
return;
Recursive(--num);
printf("Recursive call %d\n", num);
}
Recursive call 0
Recursive call 1
Recursive call 2
Recursive call 3
Recursive call 4
함수가 어떻게 중첩되어 실행되며 어떠한 순서로 실행되고 빠져나오는지 알 수 있다.
프로그램을 짜다보면 반드시 재귀함수를 사용해야만 해결할 수 있는 경우도 많으니 꼭 알아두어야한다.
이번엔 팩토리얼 계산 함수를 작성해보도록 하자.
#include <stdio.h>
int main(void) {
int fact;
printf("팩토리얼 계산기 : ");
scanf("%d", &fact);
printf("%d! = %d", fact, Factorial(fact));
return 0;
}
int Factorial(int num) {
if (num < 0)
return;
if (num == 0)
return 1;
return num * Factorial(num - 1);
}
팩토리얼 계산기 : 5
5! = 120
팩토리얼 계산기 : 6
6! = 720
Factorial 함수의 if (num < 0) 부분은 없어도 된다.