함수는 C에서 프로그램을 작성할 때 중심이 되는 부분이다. 앞에서는 이미 컴파일러의 일부분으로 제공되는 C의 라이브러리 함수의 몇 가지를 살펴보았다. 이 장에서는 프로그래머가 작성하는 사용자 정의 함수에 대해서 알아보도록 하겠다.


오늘은 다음과 같은 내용을 배울 것이다.

- 함수는 무엇이고, 어떤 내용으로 구성되는가?

- 함수와 구조화 프로그래밍의 장점

- 함수를 작성하는 방법

- 함수 내에서의 지역 변수 선언

- 함수에서 프로그램으로 결과 값을 돌려주는 방법

- 함수에 인수를 전달하는 방법


1. 함수는 무엇인가?

 : 이 장에서는 '함수란 무엇인가?'에 대한 해답을 두 가지 나누어 설명할 것이다. 우선, 함수가 무엇인지 설명하고 나서 함수의 사용 방법을 알아보도록 하자.


1.1 함수의 정의

 : 우선 함수가 무엇인지 알아보자. 함수(function)는 일정한 동작을 수행하고, 필요에 따라 함수를 호출했던 프로그램으로 결과 값을 돌려주는 C의 독립적인 코드이며, 독특한 이름을 가진다. 이것을 좀더 구체적으로 살펴보자.


 - 함수는 이름을 가진다.

: 모든 함수는 독특한 이름을 가지고 있다. 프로그램에서는 이런 이름을 사용하여 함수에 포함된 내용(코드)을 사용할 수 있게 된다. 이런 동작을 함수 호출이라고 한다. 함수는 또한 다른 함수 내에서 호출될 수 있다.


 - 함수는 독립적이다.

: 함수는 프로그램 내의 다른 부분에 의해서 영향을 받거나 또는 다른 부분에 영향을 주지 않고 주어진 동작을 수행한다.


 - 함수는 특정 동작을 수행한다.

: 이것은 아주 간단한 사실이다. 함수가 수행하는 동작은 프로그램에서 프린터로 텍스트 문장을 인쇄하거나 숫자 순서대로 배열을 정렬하고, 세제곱근을 구하는 것과 같이 프로그램의 전체적인 동작을 구성하는 일부분이자 독립된 작업 내용이다.


 - 함수는 호출한 프로그램으로 결과 값을 돌려줄 수 있다.

: 프로그램이 함수를 호출할 때에는 함수에 포함된 문장이 실행된다. 이런 함수 내의 문장은 필요할 때마다 호출한 프로그램으로 결과를 전달할 수 있다. 지금까지 설명한 내용이 함수의 '정의'이다. 함수에 대한 내용을 설명할 때 이런 내용을 기억하기 바람.


1.2 함수의 사용 예

<리스트 5.1>에 있는 프로그램에는 사용자 정의 함수가 포함되어 있다.


<리스트 5.1> 숫자의 세제곱을 계산하기 위해 함수를 사용하는 프로그램

#include <stdio.h>


long cube(long x);


long input, answer;


main(0

{

   printf("Enter an integer value: ");

   scanf("%d", &input);

   answer = cube(input);


   printf("\nThe cube of %ld is %ld.\n", input, answer);


   return 0;

}


long cube(long x)

{

   long x_cubed;


   x_cubed = x * x * x;

   return x_cubed;

}



-> 입력 / 출력
 Enter an integer value: 9
 The cube of 9 is 729.
=> cube()함수와 main()함수의 구조를 비교해본다면, 두 함수의 구조가 같다는 것을 알 수 있다. main()도 하나의 함수이다. 함수의 다른 예로는 앞에서 사용했던 printf()와 scanf()가 있다. printf()와 scanf()는 사용자 정의 함수와 달리 라이브러리 함수이지만, 사용자 정의 함수와 마찬가지로 인수를 사용하고 결과를 돌려주는 함수이다.

2. 함수의 사용
 : C 프로그램에서는 프로그램 내에서 함수를 호출할 때까지 함수 내의 문장을 실행하지 않는다. 프로그램은 함수를 호출할 때 하나 이상의 자료를 인수의 형식으로 함수에 전달할 수 있다. 인수(argument)는 함수에서 특정 동작을 수행하기 위해서 필요한 프로그램의 데이터이다. 인수를 전달받은 함수는 주어진 동작을 수행하기 위해 함수 내에 포함된 문장을 실행하게 된다. 함수 내의 모든 문장이 실행되고 나면, 프로그램에서 함수를 호출했던 부분이 다시 실행될 것이다. 함수는 필요한 경우에 복귀값의 형식으로 프로그램에 결과값을 전달할 수 있다. <그림 5.1>은 한 번씩 호출되는 세 개의 함수가 사용되는 프로그램을 보여주고 있다. 함수가 호출될 때마다 프로그램의 제어는 함수로 전달된다. 함수의 실행이 완료될 때 제어는 프로그램 내에서 함수를 호출한 부분으로 다시 전달된다. 함수는 필요한 만큼 여러 번 호출될 수 있으며, 어떤 순서로도 호출될 수 있다.

<그림 5.1> 프로그램에서 함수를 호출할 때 제어는 함수로 전달되며, 함수의 실행이 완료될 때 다시 프로그램으로 전달된다. 이제 함수에 대한 개념과 함수의 중요성을 이해할 수 있을 것이다. 잠시 후에 자신만의 함수를 작성하고 사용하는 방법을 살펴보도록 하자.

** 문법 - 함수의 작성과 사용


함수 원형

    return_type function_name(arg-type name-1, ..., arg-type name-n);


함수 정의

    return_type function_name(arg-type name-1, ..., arg-type name-n)

    {

         /* 함수의 여러 가지 문장들 */

    }


함수 원형(function prototype)은 프로그램에서 나중에 정의되는 함수에 대한 내용을 컴파일러에게 알려주고 함수가 돌려주는 복귀값의 형태를 포함한다. 또한 함수의 동작 내용을 설명하는 함수의 이름을 포함하고 있다. 함수 원형에는 함수에 전달되는 인수의 형태(arg-type)가 포함되고, 필요에 따라 함수에 전달되는 변수의 이름을 포함한다. 함수 원형은 항상 세미콜론으로 끝난다. 함수 정의(function definition)는 실제적인 함수의 내용이다. 함수 정의에는 실행될 문장이 포함된다. 함수 정의에서 첫 번째 부분은 함수 헤더 (function header)라고 하는데, 함수 원형에서 세미콜론을 제거한 것과 같은 내용을 가진다. 함수 헤더는 세미콜론으로 끝나지 않는다. 또한 함수 원형에서는 변수의 이름이 선택적으로 사용되지만, 함수 헤더에서는 반드시 포함되어야 한다. 헤더의 다음에는 함수가 수행하는 동작을 위해서 필요한 프로그램 문장이 포함된다. 함수의 주요 내용이 되는 이 부분은 중괄호 내에 포함되어야 한다. 또한, void가 아닌 다른 어떤 형태의 복귀값이 필요하다면, 지정된 형태에 일치하는 return문이 포함되어야 한다.


   함수 원형의 예


       double squared(double number);

       void print_report(int report_number);

       int get_menu_choice(void);


   함수 정의의 예


       double squared(double number)

       {

         return (number * number);

       }

 

       void print_report(int report_number)

       {

          if(report_number == 1)

            puts("Printing Report 1");

          else

            puts("Not printing Report 1");

       }

 


3. 함수와 구조화 프로그래밍

 : C 프로그램을 작성할 때에는 개별적인 프로그램 동작을 함수를 통해서 독립적으로 수행하는 구조화 프로그래밍(structured programming)을 구현할 수 있다. 프로그램의 독립된 부분에서 작업을 처리한다는 것은 앞에서도 설명한 함수의 특징과 동일하다. 함수와 구조화 프로그래밍은 아주 밀접한 관련이 있다.


3.1 구조화 프로그래밍의 장점

 : 구조화 프로그래밍이 왜 중요한가? 두 가지 중요한 이유가 있다.


- 복잡한 프로그래밍 문제를 여러 개의 단순하고 짧은 내용으로 분할하므로 구조화 프로그래밍을 통해 프로그램을 작성하는 것이 더 쉽다. 각각의 작업은 프로그램과 독립적으로 존재하는 함수에 의해서 수행된다. 이런 과정을 통해서 한 번에 하나씩 문제를 처리하며 상대적으로 쉽게 프로그램을 작성할 수 있다.


- 구조화 프로그래밍을 통해 작성한 프로그램은 디버깅이 쉽다. 만약 프로그램에 비정상적인 동작을 나타내는 버그(bug)가 포함되어 있다면, 구조화 프로그래밍에서는 특정 함수를 분리해서 문제를 쉽게 발견할 수 있다.


 구조화 프로그래밍과 관련된 다른 한 가지 장점은 시간을 절약할 수 있다는 것이다. 만약 어떤 프로그램에서 특정 동작을 수행하는 함수를 작성한다면, 이런 동작을 수행할 필요가 있는 다른 프로그램에서 그 함수를 쉽게 사용할 수 있을 것이다. 심지어 새로운 프로그램에서 약간 다른 내용의 동작을 수행할 필요가 있다면, 처음부터 새로운 함수를 작성하는 것보다 이전에 작성했던 함수를 변경하여 사용하는 것이 더 쉽다는 것을 알 수 있을 것이다. 실제로 이런 함수를 직접 작성해보지 않더라도, 지금까지 두 함수 printf()와 scanf()를 아주 많이 사용했다는 것을 생각해보기 바란다. 만약 함수가 한 가지 작업을 수행하도록 작성되어 있다면, 다른 프로그램에서 사용하기는 더 쉬어진다.


3.2 구조화 프로그래밍의 방법

 : 구조화 프로그래밍을 구현하기 원한다면, 우선 몇 가지 계획을 세울 필요가 있다. 이런 계획은 프로그램을 직접 작성하기 전에 세워야 하는 것으로, 종이와 연필만으로도 간단히 할 수 있는 일이다. 프로그래밍 계획에는 처리할 작업을 목록으로 나열해야 한다. 일단 전체적이고 포괄적인 프로그램의 동작을 생각해보자. 만약 이름과 주소록을 관리하기 위한 프로그램을 작성하려고 한다면, 프로그램이 어떤 동작을 수행해야 할까? 몇 가지 예를 들어보자.


- 새로운 이름과 주소를 입력한다.

- 현재의 내용을 변경한다.

- 성과 이름에 의한 순서대로 내용을 분류한다.

- 우편용 레이블(label)을 인쇄한다.


여기에서는 프로그램의 동작 내용을 네 가지 주요 작업으로 분할했다. 각각의 내용은 함수로 구성할 수 있다. 이제, 이것을 더욱 구체적으로 나누어보자. 예를 들어, '새로운 이름과 주소를 입력한다'는 동작은 다음과 같이 더욱 구체적으로 나타낼 수 있을 것이다.


- 디스크에서 현재의 주소록을 읽어들인다.

- 하나 이상의 새로운 내용을 입력하도록 해준다.

- 새로운 데이터를 목록에 추가한다.

- 갱신된 목록을 디스크에 저장한다.


이와 비슷하게 '현재의 내용을 변경한다.'는 동작은 다음과 같이 나누어볼 수 있다.


- 디스크에서 현재의 주소록을 읽어들인다.

- 하나 이상의 내용을 변경한다.

- 갱신된 목록을 디스크에 저장한다.


이런 두 가지 항목에서 공통적인 내용이 있다는 것을 알 수 있다. 디스크에서 데이터를 읽어들이고 갱신된 내용을 다시 디스크에 저장하는 동작은 두 가지 경우에 모두 사용된다. 그래서 '디스크에서 현재의 주소록을 읽어들이는' 하나의 함수를 작성하고, 이 함수를 '새로운 이름과 주소를 입력한다'는 함수와 '현재의 내용을 변경한다.'는 함수에서 호출할 수 있다. 또한, '갱신된 목록을 디스크에 저장하는' 함수도 똑같은 방법으로 사용할 수 있을 것이다.


여기서 구조화 프로그램의 한 가지 장점을 알 수 있을 것이다. 프로그램을 작업별로 세분화하면 프로그램 내에서 공통적으로 수행되는 작업이 무엇인지 알 수 있다. 그래서 프로그램을 작성하는데 소모되는 시간을 절약하고, 프로그램을 더욱 작고 효율적으로 만들 수 있으며, '공통적인' 디스크 작업 함수를 작성할 수 있다.이런 프로그래밍 방식은 계층적(hierarchical)으로 짜여진 프로그램의 구조를 형성한다. <그림 5.2>에서는 주소 목록 프로그램의 계층적인 프로그래밍 방식을 보여주고 있다.

<그림 5.2> 구조화 프로그래밍은 계층적으로 구성된다. 이런 과정에 의해서, 프로그램에서 수행할 필요가 있는 작업의 세분화도니 목록을 작성할 수 있다. 그리고 나서 모든 동작을 한번에 수행해야 하는 복잡한 프로그램을 작성하는 것보다는 상대적으로 더욱 간단한 각각의 함수를 한 번에 하나씩 작성할 수 있게 된다. 하나의 함수를 작성해서 정상적으로 동작하는지 확인하고 나면 다른 함수를 작성할 수 있다. 프로그램은 서서히 틀을 잡게 될 것이다.

3.3 하향식 접근

 : C 프로그래머들은 구조화 프로그래밍을 통해서 하향식 접근 방법(top-down approach)를 사용하게 된다. 이것은 프로그램의 구성 방법이 마치 나무를 뒤집어 놓은 것처럼 보이는 <그림 5.2>에서 설명한 내용이다. 프로그램의 실제 동작은 대부분 '가지의 끝 부분'에 해당하는 함수에서 수행된다. '상위'의 함수는 이런 여러 가지 함수들 중에서 필요한 것을 실행하도록 지시하는 역할을 한다. 결과적으로, 많은 C 프로그램은 프로그램의 주요 부분인 main() 함수에 적은 양의 코드만을 포함하게 되고, 프로그램의 골격을 이루는 내용은 여러 함수에 포함된다. main()에는 프로그램의 실행을 지시하는 몇 줄의 함수 호출문만 포함된다. 메뉴를 사용하는 프로그램에서는 종종 사용자가 선택한 사항에 따라서 프로그램의 실행을 분기하는 방법이 사용된다. 메뉴의 각 분기는 서로 다른 함수를 사용한다.


4. 함수의 작성

 : 함수를 작성하는 데 있어서의 첫 번째 단계는 함수의 용도를 결정하는 것이다. 일단 함수가 수행해야 하는 동작을 결정하고 나면 함수를 작성하는 실제 과정은 그리 어렵지 않을 것이다.


4.1 함수의 헤더

 : 모든 함수의 첫 번째 부분은 함수에 대한 특정 사항을 설명하는 세 개의 요소로 이루어지는 함수 헤더이다. 헤더는 <그림 5.3>에 나타나 있다. 각각의 요소에 대해서 하나씩 살펴보도록 하자.


<그림 5.3> 함수 헤더의 세 가지 요소

▶ 함수의 복귀형

 : 함수의 복귀형은 함수의 동작이 완료된 후에 함수를 호출했던 프로그램으로 돌려주는 데이터의 형태를 가리킨다. 복귀형은 C의 모든 데이터형이 될 수 있다. 즉, char, int, long. float, double이 사용될 수 있다. 또한, 아무런 값도 전달하지 않는 void형의 함수를 정의할 수 있다. 다음은 몇 가지 예이다.


        int   func1(...)    /* int형을 돌려준다. */

        float func2(...)    /* float형을 돌려준다. */

        void  func3(...)    /* 아무 것도 돌려주지 않는다. */


▶ 함수의 이름

 : 함수의 이름을 지정할 때에는 C의 변수 이름에 대한 규칙을 따라야 한다. 함수의 이름은 독특한 것이어야 하고, 다른 어떤 함수나 변수에 할당된 이름을 사용해서는 안된다. 함수가 수행하는 동작 내용을 알 수 있도록 함수의 이름을 지정하는 것이 좋다.


▶ 매개 변수 목록

 : 대부분의 함수에서는 프로그램에서 전달받는 인수(arguments)를 사용한다. 함수는 어떤 형태의 인수를 사용하는지 나타내어야 한다. 즉, 인수의 데이터가 지정되어야 한다. 함수에는 모든 C의 데이터형이 인수로 사용될 수 있다. 인수의 형태에 대한 내용은 함수 헤더에 매개 변수 목록의 형식으로 포함된다. 함수에 전달되는 각각의 인수에 대해서 매개 변수 목록에는 대응하는 내용이 포함되어야 한다. 이런 내용은 데이터형과 매개 변수의 이름이다. 예를 들어, 다음은 <리스트 5.1>의 함수에서 사용된 헤더이다.


        long cube(long x)


매개 변수 목록은 이 함수가 x라는 매개 변수로 표현되는 하나의 long형 인수를 사용한다는 것을 나타내기 위해서 long x라고 되어 있다. 하나 이상의 매개 변수가 존재한다면 각각의 변수를 쉼표로 구분해야 한다. 그래서 다음과 같은 헤더는


        void func1(int x, float y, char z)


x라는 이름의 int형 변수, y라는 이름의 float형 변수, z라는 이름의 char형 변수 등 세 개의 인수를 사용하는 함수를 정의한다. 어떤 함수에서는 아무런 인수도 사용하지 않으며, 이 때에는 매개 변수의 목록에 void를 사용해야 한다.


        void func2(void)


가끔 매개 변수와 인수의 차이점에 대해서 혼란스러울 것이다. 매개 변수(parameter)는 함수의 헤더에 포함되는 내용으로, 인수에 대응하여 '영역을 확보하는' 역할을 한다. 함수의 매개 변수는 고정적인 것이므로 프로그램이 실행되는 동안에 변경되지 않고 사용된다. 반면에, 인수는 함수를 호출하는 프로그램에 의해서 함수로 전달되는 실제값이다. 함수가 호출될 때마다 다른 인수값이 전달될 수 있다. 함수가 호출될 때마다 같은 수와 같은 형태의 인수를 전달해야 하지만, 인수의 실제 값은 달라질 수 있다. 함수는 인수에 대응하는 매개 변수의 이름을 통해서 값을 받아들인다. 예제를 통해서 이런 사실을 분명히 이해하도록 하자. <리스트 5.2>는 두 변 호출되는 하나의 함수가 포함되어 있는 아주 간단한 프로그램이다


<리스트 5.2> 인수와 매개 변수의 차이점


    #include <stdio.h>


    float x = (float) 3.5, y = (float) 65.11;


    float half_of(float k);


    main()

    {

       z = half_of(x);

       printf("The value of z = %f\n", z);


       z = half_of(y);

       printf("The value of z = %f\n", z);


       return 0;

    }


    float half_of(float k)

    {

       return (k / 2);

    }



-> 출력

   The value of z = 1.750000

   The value of z = 32.555000


<그림 5.4>에는 인수와 매개 변수의 관계가 나타나 있다.


 <그림 5.4> 함수가 호출될 때마다 인수가 함수 내의 매개 변수에 전달된다.

4.2 함수의 몸체

 : 함수의 몸체(function body)는 중괄호 내에 포함되어 있으며 함수의 헤더 다음에 나타난다. 함수의 실제 동작은 여기서 수행된다. 함수가 호출될 때, 프로그램의 제어는 함수의 몸체에서 부터 시작해서 return문을 만나거나 닫는 중괄호가 나타날 때 다시 함수를 호출한 프로그램으로 돌아간다.


▶ 지역변수

 : 함수의 몸체에서는 변수를 선언할 수 있다. 함수 내에서 선언되는 변수를 지역 변수(local variables)라고 한다. 지역적(local)이라는 것은 변수가 함수 내에서만 사용되고 프로그램의 다른 부분에서 동일한 이름으로 선언된 변수와 구분된다는 것을 뜻한다. 이것이 지역 변수의 개념이다. 이제부터 지역 변수를 선언하는 방법을 살펴보자. 지역 변수는 다른 변수에서와 동일한 규칙을 사용하여 선언할 수 있다. 또한, 지역 변수는 선언하는 동시에 초기화할 수 있다. 함수 내에서는 C의 모든 변수형을 선언할 수 있다. 다음은 함수 내에서 선언되는 네 개의 지역 변수를 사용하는 예제이다.


        int func1(int y)

        {

            int a, b = 10;

            float rate;

            double cost = 12.55;

        }


여기에서는 함수 내부에서 사용되는 지역 변수 a, b, rate, cost를 선언한다. 함수의 매개 변수도 변수의 선언에 해당되는 것이므로, 매개 변수의 목록에 있는 변수도 함수 내에서 사용될 수 있다. 함수 내에서 변수를 선언하고 사용할 때, 이 지역 변수는 프로그램의 다른 부분에서 선언된 모든 변수에 대해서 독립적이다. 심지어 다른 변수가 같은 이름을 사용하고 있더라도 서로 다른 변수이다. <리스트 5.3>에 있는 프로그램은 이런 독립성을 보여주고 있다.


<리스트 5.3> 지역 변수의 사용 

  #include <stdio.h>


    int x = 1, y = 2;


    void demo(void);


    main()

    {

       printf("\nBefore calling demo(), x = %d and y = %d.", x, y);

       demo();

       printf("\nAfter calling demo(), x = %d and y = %d.\n", x, y);


       return 0;

    }


    void demo(void)

    {

       int x = 88, y = 99;

       printf("\nWithin demo(), x = %d and y = %d.", x, y);

    }


-> 출력


   Before calling demi(), x = 1 and y = 2.

   Within demo(), x = 88 and y = 99.

   After calling demo(), x = 1 and y = 2.


여기에서 알 수 있듯이 함수의 지역 변수 x와 y는 함수의 바깥에서 선언된 전역 변수 x, y와 완전히 다르다. 함수 내에서 변수를 사용하는 경우에는 세가지 규칙이 적용된다.


- 함수에서 변수를 사용하기 위해서는 함수 헤더나 함수 내에서 선언해야 한다.

- 함수를 호출하는 프로그램에서 사용되는 값을 함수 내에서 사용하기 위해서는 인수의 형태로 전달해야 한다.

- 함수를 호출한 프로그램으로 함수의 결과값을 전달하기 위해서는 함수에서 결과값을 돌려주어야 한다.


▶ 함수의 내용

 : 함수에서 사용할 수 있는 문장의 종류나 내용에는 기본적으로 아무런 제한이 없다. 함수에서 수행할 수 없는 유일한 한가지 동작은 다른 함수를 정의하는 것이다. 그러나 순환문, if문, 할당문을 포함하여 함수에서는 C의 모든 문장을 사용할 수 있다. 또한 함수에서는 라이브러리 함수와 사용자 정의 함수를 호출할 수 있다. 함수의 길이에는 제한이 있을까? C는 함수의 길이에 대해서 아무런 제한을 두고 있지 않지만, 실용적인면에서 볼 때 함수는 상대적으로 짧은 것이 좋다. 구조화 프로그래밍에서 각각의 함수는 상대적으로 간단한 하나의 작업을 수행하는 것이 좋다는 사실을 기억하기 바란다. 함수의 길이가 상당히 길어진다면 하나의 함수로 여러 가지 동작을 수행하려고 했기 때문이다. 이런 함수는 아마도 두 개 이상의 더욱 세분화된 함수로 나눌 수 있을 것이다. 그렇다면, 어느 정도의 길이가 긴 것일까? 여기에 대한 정확한 해답은 없지만, 실제 경험에 의하면 25줄이나 30줄 이상의 함수를 작성하는 경우는 거의 없고 찾아보기도 힘들다. 함수의 길이에 대해서는 스스로 판단해야 한다. 어떤 프로그램에서는 긴 함수가 필요할 것이고, 대부분의 함수에서는 몇 줄의 문장만이 필요할 것이다. 프로그래밍 경험을 쌓아감에 따라, 더욱 세분화되니 함수를 사용해야 하는 경우와 어느 정도의 길이를 가지는 함수를 작성하여 사용해야 하는 경우를 구분할 수 있게 될 것이다.


▶ 값의 전달

 : 함수의 결과 값을 프로그램으로 돌렺주기 위해서는 return문과 함께 C의 수식을 사용해야 한다. 제어가 return문에 도달할 때, 수식은 평가되고 제어는 다시 원래의 프로그램으로 전달된다. 함수의 복귀값은 수식의 결과이다. 다음 함수를 살펴보자.


        int func1(int var)

        {

           int x;

           return x;

        }


이 함수가 호출될 때 return문까지의 함수의 내용이 실행된다. return은 함수를 마치고 처음에 함수를 호출했던 프로그램으로 x라는 값을 돌려준다. return에는 모든 C의 수식을 사용할 수 있다. 함수는 여러 개의 return문을 가질 수 있다. 그러나 실제로 효과를 나타내는 것은 가장 먼저 실행되는 return문이다. <리스트 5.4>에서 볼 수 있듯이 여러 개의 return문을 사용하는 것은 함수에서 다양한 값을 돌려주는 호과적인 방법이다.


<리스트 5.4> 함수에서 여러 개의 return문을 사용하는 예

    #include <stdio.h>


    int x, y, z;


    int larger_of(int, int);


    main()

    {

       puts("Enter two different integer values: ");

       scanf("%d%d", &x, &y);


       z = larger_of(x, y);


       printf("\nThe larger value is %d.\n", z);


       return 0;

    }


    int larger_of(int a, int b)

    {

       if(a > b)

         return a;

       else

         return b;

    }


-> 입력 / 출력

   Enter two different integer values:
   300
   200
   The larger value is 300.

4.3 함수의 원형
 : 프로그램에는 사용되는 각각의 함수에 대한 함수 원형이 포함되어야 한다. <리스트 5.1>의 4변째중에는 함수 원형이 나타나 있고 앞에서 다른 여러 프로그램의 함수 원형을 보았다. 함수 원형은 무엇이고 왜 사용해야 하는 것일까? 앞의 예제에서, 함수의 원형은 함수의 헤더에 세미콜론을 추가한 것과 같다는 것을 알 수 있다. 함수 헤더와 마찬가지로 함수의 원형에는 함수의 복귀형, 이름, 매개 변수에 대한 내용이 포함된다. 함수 원형이 하는 일은 컴파일러에게 함수의 복귀형, 이름, 매개 변수에 대해서 알려주는 것이다. 컴파일러는 이런 자료를 이용하여 소스 코드에서 함수를 호출할 때마다 함수를 찾아서 함수에 전달되는 인수의 수와 형태를 확인하고 함수가 돌려주는 복귀값이 정확한지 검사할 수 있다.
 만약 이런 과정에서 문제가 발생하면 컴파일러는 에러 메시지를 출력한다. 실제로 함수 원형이 함수의 헤더와 정확히 일치할 필요는 없다. 동일한 형태와 순서의 매개 변수를 사용하고 동일한 개수의 매개 변수를 포함시킨다면 변수의 이름은 달라질 수 있다. 헤더와 원형의 내용이 반드시 일치할 필요는 없다. 그러나 헤더와 원형을 똑같이 작성하면 프로그램을 더욱 이해하기 쉽다. 함수 정의를 마칠 때 함수의 원형을 작성하기 위해서 헤더를 복사해서 에디터에서 제공되는 잘라 붙이기 기능을 사용하면 된다. 마지막에는 반드시 세미콜론을 추가하기 바란다. 함수 원형은 소스 코드의 어디에 위치되어야 하는 것일까? 함수 원형은 main()이 시작되기 전이나 또는 첫 번째 함수가 정의되기 전에 포함되어야 한다. 프로그램을 이해하기 쉽게 하려면 모든 함수원형을 한군데 모아두는 것이 가장 좋다.

5. 함수에 인수 전달하기
 : 함수에 인수를 전달하기 위해서는 함수의 이름 다음에 나타나는 괄호 내에 인수를 포함시 켜야 한다. 인수의 수와 형태는 함수 헤더나 원형에 나타나 있는 것과 일치해야 한다. 예를 들어, 함수가 두 개의 int형 인수를 사용하도록 정의되어 있다면, 더 많거나 적지 않고 데이터형이 다르지 않은 두 개의 int형 인수를 전달해야 한다. 만약 함수에 부적절한 형태나 개수의 인수를 전달하려고 한다면, 컴파일러는 함수 원형에 나타나 있는 내용을 근거로 하여 문제를 찾을 것이다.
 함수가 여러 개의 인수를 사용할 경우 함수 호출에 포함되는 인수는 순서대로 함수의 매개 변수에 할당된다. <그림 5.5>에 나타나듯이 첫 번째 인수는 첫 번째 매개 변수에 대응하고, 두 번째 인수는 두번째 매개 변수에 대응하는 순서로 전달된다.

<그림 5.5> 여러 개의 인수는 순서대로 함수의 매개 변수에 할당된다.

각각의 인수는 C에서 사용할 수 있는 어떤 수식이든지 될 수 있다. 상수, 변수, 산술이나 논리 수식, 또는 심지어 하나의 return값을 가지는 다른 함수를 사용할 수도 있다. 예를 들어, half(), square(), third()가 모두 복귀값을 가지는 함수라면 다음과 같은 문장을 작성할 수 있다.


      x = half ( third ( square ( half ( y ) ) ) );


프로그램은 우선 y라는 인수를 전달하며 함수 half()를 호출한다. half()의 실행이 완료되면 half()의 복귀값을 인수로 사용하여 square()가 호출된다. 다음으로 square()의 복귀값을 인수로 사용하여 third()가 호출된다. 그리고 나서 다시 half()가 호출되며, 이번에는 third()의 복귀값을 인수로 사용하게 된다. 마지막으로 half()의 복귀값은 변수 x에 할당된다. 다음은 이와 같은 내용의 문장을 여러 개로 나눈 것이다.


       a = half(y);

       b = square(a);

       c = third(b);

       x = half(c);


6. 함수의 호출

 : 함수는 두 가지 방법으로 호출할 수 있다. 어떤 함수는 단순히 함수 이름과 인수만을 사용하여 한 문장으로 호출할 수 있다. 만약 함수가 복귀값을 가진다면 그 값은 버려진다.


       wait(12);


두 번째 방법은 복귀값을 생성하는 함수에서만 사용할 수 있다. 이런 함수는 결과적인 값, 즉 복귀값을 전달하므로 C의 수식으로 간주된다. 그래서 C의 수식을 사용할 수 있는 곳이라면 어디에서든지 사용할 수 있다. 앞에서는 이미 복귀값을 생성하며 할당문의 오른쪽에서 사용되는 수식의 예를 보았다. 몇 가지 다른 예를 살펴보도록 하자. 다음 예제에서 half_of()는 함수의 매개 변수이다.


       printf("Half of %d is %d." x, half_of(x));


우선, x를 인수로 하여 함수 half_of()가 호출되고, printf()는 x와 half_of(x)의 결과를 인수로 사용하여 호출된다. 두 번째 예에서는 하나의 수식 네에서 여러 함수를 사용하고 있다.

  

       y = half_of + half_of(z);


여기에서는 half_of()를 두 번 사용하고 있지만, 두 번째 함수는 다른 함수가 될 수도 있을 것이다. 동일한 내용을 여러 문장으로 나누면 다음과 같다.


       a = half_of(x);

       b = half_of(z);

       y = a + b;


마지막 두 예제는 함수의 복귀값을 사용하는 효과적인 방법을 보여준다. 다음은 if문 내에서 사용된 함수이다.


       if (half_of(x) > 10)

       {

          /* 어떤 문장이 될 수 있다 */

       }


함수의 복귀값이 기준에 일치한다면, 즉 half_of()가 10보다 큰 값을 돌려주면 if문은 참이 되고 그 아래에 있는 문장이 실행된다. 그러나 함수의 복귀값이 조건을 만족시키지 않는다면 if문의 내용은 실행되지 않는다. 다음은 더 좋은 예이다.


       if (do_a_process() != OKAY)

       {

          /* 에러 처리 루틴 */

       }


여기서 사용된 예제도 실제 프로그래밍 예제는 아니다. 그러나 이 문장은 함수가 정상적으로 실행되었는지를 확인하기 위해서 복귀값을 사용하는 중요한 예제이다. 만약 정상적이지 않은 결과가 나타나면 if문에서는 에러 처리 루틴이 실행된다. 이것은 파일에서 자료를 읽어들이고, 값을 비교하고, 메모리를 할당하는 경우에 흔히 사용되는 에러 처리 방법이다. 또한, void의 복귀형을 가지는 함수를 수식에서 사용하게 되면 컴파일러는 에러 메시지를 출력할 것이다.


6.1 재귀 용법

 : 재귀 용법(reacquisition)은 함수가 직접적으로나 간접적으로 그 자신을 호출하는 것을 말한다. 간접적인 재귀(indirect reacquisition)는 하나의 함수를 호출하는 다른 어떤 함수가 현재의 함수 내에서 호출되는 경우를 말한다. C에서는 재귀적인 함수의 사용을 허용하는데, 재귀 용법은 가끔 매우 유용하게 사용된다. 예를 들어, 재귀 용법은 숫자의 계승(factorial)을 계산하는 데 유용하다. 숫자 x의 계승은 x!로 표기하는데 다음과 같이 계산할 수 있다.


      x! = x * (x - 1) * (x - 2) * (x - 3) * ... * (2) * 1


x!는 다음과 같이 계산할 수 있다.


      x! = x * (x - 1)!


계산을 한 번 진행하면 (x-1)!은 동일한 방법으로 계산할 수 있을 것이다.


      (x - 1)! = (x - 1) * (x - 2)!


이런 공식을 사용하여 x가 1이 될 때까지 재귀적인 방법으로 계산을 진행하게 된다. <리스트 5.5>에 있는 프로그램은 계승을 계산하기 위해서 재귀적 함수를 사용한다. 프로그램이 unsigned int형을 사용하므로 계승을 구할 숫자값은 8까지만 허용된다. 9이상의 계승의 결과는 정수형 변수의 범위를 벗어난다.


<리스트 5.5> 계승을 계산하기 위한 재귀적 함수의 사용

  #include <stdio.h>


    unsigned int f, x;

    unsigned int factorial(unsigned int a);


    main()

    {

       puts("Enter an integer value between 1 and 8: ");

       scanf("%d", &x);


       if(x > 8 || x < 1)

       {

          printf("Only values from 1 to 8 are acceptable!\n");

       }

       else

       {

          f = factorial(x);

          printf("%u factorial equals %u\n", x, f);

       }


       return 0;

    }


    unsigned int factorial(unsigned int a)

    {

       if(a == 1)

         return 1;

       else

       {

         a *= factorial(a - 1);

         return a;

       }


-> 입력 / 출력


   Enter an integer value between 1 and 8:

   6

   6 factorial equals 720}


7. 함수의 위치

 : 프로그램에서 함수를 어디에 위치시키는 것이 좋을까? 여기에서는 함수를 main()과 같은 파일 내에서 main() 함수의 뒤에 위치시키는 것이 좋다고 알아 두기 바란다. <그림 5.6>에는 함수를 사용하는 프로그램의 기본적인 구조가 나타나 있다. 실제로는 사용자 정의 함수를 main()이 아닌 독립된 소스 코드 파일에 포함시킬 수 있다. 이런 방법은 큰 프로그램을 작성하거나 하나 이상의 프로그램에서 동일한 기능의 함수를 사용하기 원할 때 유용하다.


<그림 5.6> 함수의 원형을 main()의 앞 부분에 위치시키고, 함수 정의 내용을 main()의 뒷 부분에 위치시킨다.



'C 언어' 카테고리의 다른 글

3장 데이터 저장하기 : 변수와 상수  (0) 2019.06.02
4장 문장, 수식, 연산자  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02
7장 입출력의 기초  (0) 2019.06.02
8장 숫자 배열 사용하기  (0) 2019.06.02

+ Recent posts