Programming/C

C언어 - 함수(Function)

JunsuKim 2021. 9. 22.
728x90

함수를 만드는 이유

해결해야 할 규모가 크거나 형태가 복잡한 경우 구현에 필요한 기능들을 분석하고, 그 분석 결과를 바탕으로 작은 크기의 함수들로 구현한다. 한마디로 "하나씩 천천히 해나가는 것"이다. 

기능들을 나눠놓으면 문제의 발생 및 프로그램의 요구사항 변경으로 인한 소스코드의 변경이 필요할 때, 변경의 범위를 축소 및 제한할 수 있다.

함수에는 전달 인자의 유무와 반환 값의 유무에 따른 네 가지의 형태가 있다.

  • 1. 전달인자가 있고, 반환 값이 있다.
  • 2. 전달인자가 있고, 반환 값이 없다.
  • 3. 전달인자가 없고, 반환 값이 있다.
  • 4. 전달인자가 없고, 반환 값이 없다.

이러한 유형들의 함수들을 정의해 보겠다.

1. 전달인자와 반환 값이 모두 있는 경우

int Add(int n1, int n2) {
    int result = n1 + n2;
    return result;
}

이는 덧셈 기능을 구현한 함수이다.

전달 인자로 int형 정수인 n1과 n2를 받으며, result변수에 두 정수를 더한 값을 저장하고 result를 반환하였다.

#include<stdio.h>

int Add(int n1, int n2) {
    return n1 + n2;
}

int main() {
    printf("%d", Add(2, 3));
    return 0;
}

이와 같이 main함수에서 Add함수의 매개변수에 값을 정해주면 Add함수에서 매개변수가 값을 전달받아 2 + 3이라는 값을 반환한다.

2. 전달인자 또는 반환 값이 없는 경우

void ShowAddResult(int num) {
    printf("덧셈결과 출력: %d\n", num);
}

위는 전달인자가 있고, 반환 값이 없는 코드이다. printf를 대신할 수 있는 함수로 정의해 보겠다.

printf 함수는 서식을 지정해야 하기 때문에 상대적으로 빈번히 호출하기가 번거롭다.

하지만 이러한 함수를 만들었기 때문에 서식지정을 하지 않고 함수를 호출하면 된다.

 

int ReadNum() {
    int num = 0;
    scanf("%d", &num);
    return num;
}

이번에는 전달인자가 없고 반환 값이 있는 코드이다. 이는 scanf를 대신할 수 있는 함수로 정의해 보았다.

printf와 같이 scanf도 서식 지정을 계속해서 해주는 게 번거로우니 함수를 통해 호출을 하도록 하자.

3. 전달인자와 반환 값이 모두 없는 경우

void Question() {
    printf("두개의 정수를 입력하시오: ");
}

이와 같은 함수는 단순히 메시지를 전달하는 함수로 전달 인자와 반환 값이 모두 필요 없다.

이러한 작은 코드들을 한 번에 모아 보면 다음과 같이 된다.

#include<stdio.h>

void Question() {
    printf("두개의 정수를 입력하시오: ");
}

int Add(int num1, int num2) {
    return num1 + num2;
}

void ShowAddResult(int result) {
    printf("덧셈결과 : %d\n", result);
}

int ReadNum(){
    int num = 0;
    scanf("%d", &num);
    return num;
}

int main() {
    Question();
    num1 = ReadNum();
    num2 = ReadNum();
    int result = Add(num1, num2);
    ShowAddResult(result);
    return 0;
}

이와 같이 함수들을 나누어 하나씩 해결하다 보면 큰 프로젝트를 보기 쉽게 나눌 수 있다.

재귀함수(Recursive Function)

재귀함수란 함수 내에서 자기 자신을 다시 호출하는 함수이다.

void Recursive() {
    printf("Recursive call!\n");
    Recursive();
}

이해가 잘 되지 않는다면 위의 사진과 같이 복사본이 실행되는 구조라 생각하면 된다.

"Recursive 함수를 실행하는 중간에 다시 Recursive 함수가 호출되면, Recursive함수의 복사본을 하나 더 만들어서 복사본을 실행하게 된다."

 

예제를 보며 확인해보자.

#include<stdio.h>

void Recursive(int num) {
    if(num <= 0) return;
    printf("%d\n", num);
    Recursive(num-1);
}

int main() {
    Recursive(3);
    return 0;
}

// 3 2 1

위에서 보면 Recursive함수에서 num이 0보다 같거나 작으면 함수가 종료되도록 하는 "탈출 조건"을 만들어 주었다.

main함수에서 Recursive함수에 3을 전달인자에 주었고, 이를 통해 num이 0보다 클 시 출력이 되며 재귀함수를 통해 num-1이라는 값을 전달인자가 또 다시 받게 된다. 반복을 하다 num이 0의 값이 된다면 함수가 끝나게 되는 것이다.

 

재귀함수는 자료구조나 알고리즘의 어려운 문제를 단순화하는 데 사용되므로 중요한 역할을 하고 있다. 또한 재귀적인 수학적 수식을 그대로 코드로 옮길 수 있다. 예를 들어 팩토리얼(Factorial) 값을 반환하는 함수를 재귀적으로 구현해보자.

 

팩토리얼이란

n! = n * (n-1) * (n-2) * (n-3) * . . . . . * 2 * 1이라는 수식이다.

따라서 5! = 5 * 4 * 3 * 2 * 1인 120이 된다.

이러한 알고리즘을 디자인해보면,

수식적으로 이렇게 표현할 수 있다.

따라서 n이 1 이상일 경우 n * f(n-1)의 값을 반환하면 된다.

if(n >= 1) return n * Factorial(n-1);

또한 n이 0인 경우의 결과값은 1이므로 아래와 같이 된다.

if(n == 0) return 1;

이를 if~else를 통해 묶으면

if(n == 0) return 1;
else return n * Factorial(n-1);

이와 같이 된다. 이로써 팩토리얼의 값을 반환하는 Factorial함수를 완성한 것이다.

완전한 코드를 통해 결과값이 제대로 나오는지 알아보겠다.

#include<stdio.h>

int Factorial(int num) {
    if(num == 0) return 1;
    else return n * Factorial(num - 1);
}

int main() {
    for(int i=1; i<=5; i++) printf("%d! = %d\n", i, Factorial(i));
    return 0;
}

/*
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
*/

*재귀함수는 함수의 코드를 이해하는 것보다 그 과정을 이해하는 것이 더 중요하다.

728x90

댓글