아홉번째 강의 "포인터에 대해서"에서는 C 프로그래밍 언어의 가장 중요한 주제인 포인터에 대한 기본적인 내용을 다루었다. 이 장에서는 더욱 유용한 프로그램을 작성할 수 있도록 포인터에 대한 좀더 많은 내용을 설명할 것이다. 오늘은 다음과 같은 내용을 배울 것이다.

·포인터에 대한 포인터를 선언하는 방법
·다차원 배열과 포인터를 함께 사용하는 방법
·포인터의 배열을 선언하는 방법
·함수에 대한 포인터를 선언하는 방법
·데이터 저장을 위한 링크드 리스트를 생성하기 위해 포인터를 사용하는 방법

1. 포인터에 대한 포인터
: 포인터는 다른 어떤 변수의 주소값을 가지는 숫자 변수이다. 포인터는 간접 연산자(*)를 사용하여 선언할 수 있다. 예를 들어, 다음 문장은

   int *ptr;

int형 변수를 지적하는 ptr이라는 이름의 포인터를 선언한다. 이런 포인터를 선언하고 나면 대응하는 형태의 어떤 변수를 지적하도록 하기 위해서 주소 연산자(&)를 사용한다. x가 int형 변수로 선언되어 있다고 가정한다면, 다음 문장은

   ptr = &x;

x의 주소를 ptr에 할당하여 ptr이 x를 지적하도록 한다. 또한, 간접 연산자를 사용하면 포인터가 지적하는 변수의 값을 참조할 수 있다. 다음 문장은 모두 x에 12의 값을 저장한다.

   x = 12;
   *ptr = 12;

포인터 자체는 숫자 변수이므로 컴퓨터 내의 메모리에서 특정 주소에 저장된다. 그래서 포인터에 대한 포인터, 즉 포인터 변수의 값이 다른 포인터의 주소인 변수를 생성할 수도 있다. 다음은 예이다.

   int x = 12;                /* x는 int형 변수이다. */
   int *ptr = &x;             /* ptr은 x에 대한 포인터이다. */
   int **ptr_to_ptr = &ptr;   /* ptr_to_ptr은 int형 변수에 대한 포인터의 포인터이다. */

포인터의 포인터를 선언할 때에는 이중 간접 연산자(**)를 사용한다는 것을 기억하자. 또한, 포인터의 포인터가 지적하는 변수를 참조할 때에도 이중 간접 연산자를 사용한다. 그래서 다음의 문장은

   **ptr_to_ptr = 12;

변수 x에 12의 값을 할당하고, 다음 문장은

   printf("%d", **ptr_to_prt);

x의 값을 화면 상에 출력한다. 여기에서 실수로 하나의 간접 연산자를 사용하면 에러가 발생한다. 다음 문장은

   *ptr_to_ptr = 12;

ptr에 12의 값을 할당하고 ptr은 이미 다른 어떤 값이 저장되어 있는 주소 12를 지적하게 된다. 이것은 분명히 잘못된 것이다.

포인터의 포인터를 선언하고 사용하는 것을 이중 간접 사용(multiple indirection)이라고 한다. <그림 15.1>에는 변수, 포인터, 포인터에 대한 포인터의 관계가 나타나 있다. 간접 사용의 단계에는 아무런 제한이 없다. 필요하다면 무한한(ad_infinitum) 단계의 포인터의 포인터를 사용할 수도 있지만, 일반적으로 2단게를 초과하는 포인터의 사용에는 특별한 장점이 없다. 오히려 복잡해짐에 따라 실수가 발생할 가능성만 높아진다.


<그림 15.1> 포인터의 포인터를 설명하는 그림

그렇다면 포인터의 포인터는 어떤 경우에 사용될까? 포인터의 포인터는 이 장의 후반부에서 설명할 포인터의 배열에서 가장 많이 사용된다.

2. 포인터와 다차원 배열
: 8번째 강의 "숫자 배열 사용하기"에서는 포인터와 배열간의 특별한 관계에 대해서 설명했었다. 특히, 대괄호를 포함하지 않는 배열의 이름이 배열의 첫 번째 요소에 대한 포인터라는 사실은 중요하다. 결과적으로, 특정 형태의 배열을 참조할 때에는 포인터식 표기 방법을 사용하는 것이 낫다. 그러나 지금까지의 예제는 일차원 배열에만 국한된 것이었다. 다차원 배려의 경우는 어떨까? 다차원 배열은 각각의 차원에 대해 대괄호를 사용하여 선언된다는 것을 기억하자. 예를 들어, 다음 문장은 8개의 int형 변수를 가지는 2차원 배열을 선언한다.

   int multi[2][4];

배열은 행과 열을 가지는 것으로 생각할 수 잇다. 앞의 배열은 2행 4열로 구성된다. 그러나 다차원 배열의 구조를 표현하는 다른 한 가지 방법이 있다. C가 실제로 배열을 다루는 방법에 더 가까운 것으로 multi를 두 개의 요소를 가지는 배열로 생각할 수 있다. 각각의 요소는 4개의 정수를 가지는 배열이다. 이런 사실을 이해하기 어렵다면 배열의 선언문을 4개의 구성 요소로 나누어서 설명하고 있는 <그림 15.2>를 참고하기 바란다.


<그림 15.2> 다차원 배열 선언문의 구성 요소

각각의 구성 요소는 다음과 같은 뜻을 가진다.

1. multi라는 이름의 배열을 선언한다.
2. 배열 multi는 두 개의 요소를 가진다.
3. 각각의 요소는 다시 네 개의 요소를 가지고 있다.
4. 네 개의 요소는 int형이다.

다차원 배열의 선언문은 배열의 이름에서부터 시작하여 오른쪽으로 이동하며 대괄호 내에 포함된 내용을 처리한다. 마지막 대괄호의 내용이 처리되고 나면 배열의 기본 데이터형을 결정하기 위해 선언문의 시작 부분으로 이동하게 된다.

이 장에서는 포인터에 대한 내용을 다루므로, 이제 원래의 주제인 포인터로 사용되는 배열의 이름에 대해서 다시 살펴보자. 1차원 배열에서와 마찬가지로 다차원 배열의 이름은 배열의 첫 번째 요서에 대한 포인터이다. 앞에서 사용된 예제의 경우, multi는 int multi[2][4]를 통해서 선언된 2차원 배열의 첫번째 요소에 대한 포인터이다. multi의 첫번째 요소는 정확히 무엇일까 multi의 첫번째 요소는 int형 변수인 multi[0][0]이 아니라 multi가 배열을 가지는 배열이므로 네 개의 int형 변수를 가지는 배열 multi[0]이라는 것을 기억하자. multi[0]은 multi에 포함된 두 배열의 하나이다. 또한, multi[0]이 하나의 배열이라면 어떤 값을 지적하는가? 실제로, multi[0]은 첫 번째 요소인 multi[0][0]을 지적한다. 이런 사실에 대해서는 의문을 가질 수 있다. 대괄호를 포함하지 않는 배열의 이름이 배열의 첫번째 요소에 대한 포인터라는 것을 기억하자. multi[0]은 대괄호를 포함하고 있는 multi[0][0]의 이름이므로 하나의 포인터라고 볼 수 있다. 지금까지의 내용이 혼란스럽더라도 걱정할 필요는 없다. 사실, 포인터와 배열의 관계는 이해하기 어려운 내용이다. n차원의 배열을 사용할 때 다음과 같은 규칙을 기억한다면 유용할 것이다.

·n개의 대괄호와 적절한 색인을 포함하는 배열의 이름은 배열의 데이터를 뜻한다. 즉, 지정된 배열의 요소에 저장된 데이터를 나타내는 것이다.

·n개 이하의 대괄호를 포함하는 배열의 이름은 배열의 요소에 대한 포인터를 뜻한다. 그래서 앞의 예제에서는 multi가 포인터이고, multi[0]도 포인터이며, multi[0][0]은 배열의 데이터를 나타내는 것이다.

 이제, 이런 모든 포인터가 실제로 지적하는 것이 무엇인지 살펴보도록 하자. <리스트 15.1>에 있는 프로그램은 앞에서 사용된 것과 비슷한 2차원 배열을 선언하고 관련된 포인터의 값을 출력한다. 또한 첫 번째 배열 요소의 주소를 출력한다.

<리스트 15.1> 다차원 배열과 포인터의 관계

 /* 포인터와 다차원 배열의 사용 예 */


 #include <stdio.h>


 int multi[2][4];


 main()

 {

    printf("\nmulti = %u", multi);

    printf("\nmulti[0] = %u", multi[0]);

    printf("\n&multi[0][0] = %u", &multi[0][0]);

    return(0);

 }

=> 실제 값을 시스템에 따라 1.328이 아닐 수도 있겠지만 세 값이 동일하다는 사실은 변함 없다. 배열 multi의 주소는 배열 multi[0]의 주소와 동일하고, 이런 값은 배열 multi[0][0]에 저장되어 있는 첫 번째 정수값의 주소와 동일하다. 세 포인터가 동일한 값을 가진다면 프로그램의 측면에서 실제 차이점은 무엇일까? 9번째 강의에서는 포인터가 지적하는 것을 C 컴파일러가 '알고 있다'고 설명했다. 더욱 정확히 표현하자면, 컴파일러는 포인터가 지적하는 항목의 크기를 알고 있다.
 앞에서 사용된 각 항목은 어떤 크기를 가지고 있을까? <리스트 15.2>는 이런 각 항목의 크기를 바이트 단위로 출력하기 위해서 sizeof() 연산자를 사용하고 있다.

<리스트 15.2> 각 항목의 크기 확인하기

 /* 다차원 배열 요소의 크기 */


 #include <stdio.h>


 int multi[2][4];


 main()

 {

    printf("\nThe size of multi = %u", sizeof(multi));

    printf("\nThe size of multi[0] = %u", sizeof(multi[0]));

    printf("\nThe size of multi[0][0] = %u", sizeof(multi[0][0]));

    return(0);

 }

=> IBM의 OS/2와 같은 32비트 운영체제를 사용중이라면 결과는 32, 16, 4가 될 것이다. 이것은 OS/2와 같은 운영체제에서 int형이 4바이트이기 때문이다. 결과에 대해서 생각해보자. 배열 multi는 네 개의 정수값을 가지는 두 개의 배열을 포함하고 있다. 각각의 정수는 2바이트를 차지한다. 전체적으로는 8개의 정수가 존재하므로 16바이트 라는 크기는 정확한 값이다. 다음으로, multi[0]은 네 개의 정수를 가지는 배열이다. 각각의 정수는 2바이트를 차지하므로 multi[0]의 크기가 8바이트로 표현된 것도 정확하다. 마지막으로, multi[0][0]은 정수이므로 정수형의 크기는 당연히 2바이트이다.

 이제, 이런 결과에 유의해서 9번째 강의에서 설명한 포인터 연산에 대해 생각해보도록 하자. C 컴파일러는 포인터가 지적하는 값의 크기를 '알고' 잇고 포인터 연산에서는 이런 크기를 사용한다. 포인터를 증가시키면 현재 지적하고 있는 어떤 내용의 '다음 위치에 있는 것'을 지적하기 위해서 필요한 만큼 증가된다. 즉, 포인터가 지적하고 있는 값의 크기만큼 증가된다.

 이런 포인터 연산의 개념을 앞의 예제에 적용해보자. multi는 8개의 요소를 가지는 정수형 배열에 대한 포인터로 크기는 8이다. 만약 multi를 증가시키면 포인터의 값을 네 개의 요소를 가지는 정수형 배열의 크기인 8만큼 증가된다. multi가 multi[0]을 지적하고 있다면 (multi + 1)은 multi[1]을 지적할 것이다. <리스트 15.3>에 있는 프로그램은 이런 사실을 증명한다.

<리스트 15.3> 다차원 배열에서의 포인터 연산 

 /* 다차원 배열에 대한 포인터로 포인터 연산하기 */


 #include <stdio.h>


 int multi[2][4];


 main()

 {

    printf("\nThe value of (multi) = %u", multi);

    printf("\nThe value of (mulit + 1) = %u", (multi + 1));

    printf("\nThe address of multi[i] = %u", &umlti[1]);

    return(0);

 }

=> 정확한 값은 시스템에 따라 달라질 수 있지만 개념은 같을 것이다. multi를 1증가시키면 실제 값은 8(32비트 환경에서는 16) 증가되고 배열의 다음 요소인 multi[1]을 지적하게 된다. 이 예제에서는 multi가 multi[0]에 대한 포인터라는 사실을 알 수 있다. 또한, multi[0] 자체는 multi[0][0]에 대한 포인터라는 것을 알 수 있다. 그래서 multi는 포인터에 대한 포인터이다. 배열의 데이터를 참조하는 경우에 multi를 사용하기 위해서는 이중 간접 연산자를 사용해야 한다. multi[0][0]에 저장된 값을 출력하기 위해서는 다음과 같은 세 문장의 하나를 사용할 수 있을 것이다.

   printf("%d", multi[0][0]);
   printf("%d", *multi[0]);
   printf("%d", **multi);

이런 개념은 3차원 이상의 배열에도 적용된다. 그래서 3차원 배열은 2차원 배열을 요소로 가지는 배열이라고 할 수 있다. 다시, 각각의 요소는 1차원 배열을 요소로 가진다. 여기서 설명한 다차원 배열과 포인터에 대한 내용은 다소 혼란스러울 것이다. 다차원 배열을 사용할 때에는 한 가지 사실만 기억하자. n차원의 배열은 n - 1차원의 배열을 요소로 가진다. n의 값이 1일 때 배열의 요소는 배열 선언문의 앞에서 지정한 데이터형의 변수이다. 지금까지는 포인터 상수이고 변경이 불가능한 배열의 이름을 사용하였다. 그렇다면 다차원 배열의 요소를 지적하는 포인터 변수를 어떻게 선언할 수 있을까? 2차원 배열을 선언하는 앞의 예를 다시 한 번 살펴보자.

   int multi[2][4];

multi의 각 요소, 즉 네 개의 요소를 가지는 정수형 배열을 지적할 수 있는 포인터 변수를 선언하기 위해서는 다음과 같은 문장을 작성할 수 있다.

   int (*ptr)[4];

그리고 나서 다음의 문장을 사용하여 ptr이 multi의 첫 번째 요소를 지적하도록 할 수 있다.

   ptr = multi;

포인터 선언에서 괄호를 사용한 이유를 이해할 수 있는가? 대괄호([])는 포인터 연산자(*)보다 우선 순위를 가진다. 만약 다음과 같은 문장을 작성했다면

   int *ptr[4];

int형에 대한 네 개의 포인터의 배열을 선언할 것이다. 사실, 포인터의 배열을 선언하고 사용할 수는 있다. 그러나 여기서 필요한 것은 포인터의 배열이 아니다. 다차원 배열의 요소에 대한 포인터를 어떻게 사용할 수 있을까? 일차원 배열에서와 마찬가지로 포인터는 함수에 배열을 전달하는 경우에 사용된다. 이런 사실은 함수에 다차원 배열을 전달하는 두 가지 방법을 사용하고 잇는 <리스트 15.4>에서 설명된다.

<리스트 15.4> 포인터를 사용하여 다차원 배열을 함수에 전달하기  
 

 /* 다차원 배열에 대한 포인터를 함수에 전달하는 예 */


 #include <stdio.h>


 void printarray_1(int (*ptr)[4]);

 void printarray_2(int (*ptr)[4], int n);


 main()
 {

    int multi[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};


    /* ptr은 4 정수 배열에 대한 포인터이다. */


    int (*ptr)[4], count;


    /* ptr이 multi의 첫 요소를 지적하게 한다. */


    ptr = multi;


    /* 순환문에서 ptr은 multi의 다음요소, 즉 다음 4요소 정수배열을 지적하도록 증가된다. */


    for(count = 0; count < 3; count++)

       printarray_1(ptr++);


    puts("\n\nPress Enter...");

    getchar();

    printarray_2(multk, 3);

    printf("\n");

    return(0);

 }


 void printarray_1(int (*ptr)[4])

 {

    /* 4 요소 정수 배열의 요소들을 출력한다. */

    /* p는 INT형에 대한 포인터이다. */

    /* P를 PTR의 주소와 같게 하기 위해 형 변환을 사용해야 한다. */


    int *p, count;

    p = (int *)ptr;


    for(count = 0; count < 4; count++)

       printf("\n%d", *p++);

 }


 void printarray_2(int (*ptr)[4], int n)

 {

    /* n번째 4 요소 정수 배열의 요소들을 출력한다. */


    int *p, count;

    p = (int *)ptr;


    for(count = 0; count < (4 * n); count++)

       printf("\n%d", *p++);

 }

3. 포인터의 배열
 : 8번째 강의 "숫자 배열 사용하기"에서는 배열이 동일한 데이터형을 사용하고 같은 이름으로 사용되는 집단적인 데이터 저장 영역이라고 설명했다. 포인터는 C에서 제공되는 한 가지 데이터형이므로 포인터의 배열을 선언하고 사용할 수 있다. 포인터의 배열은 특별한 상황에서 매우 유용할 수 있다. 아마도 포인터의 배열을 가장 많이 사용하는 경우는 문자열을 처리할 때일 것이다. 10번째 강의 "문자와 문자열"에서 배웠듯이 문자열은 메모리에 저장되는 일련의 문자들을 말한다. 문자열의 시작 부분은 첫 번째 문자에 대한 포인터, 즉 char형에 대한 포인터로 지적된다. 문자열의 마지막은 널 문자로 구분된다.. char형에 대한 포인터의 배열을 선언하고 초기화하면 포인터의 배열을 통해서 방대한 양의 문자열을 사용하거나 처리할 수 있다. 배열의 각 요소는 서로 다른 문자열을 가리키므로 배열을 통해서 순환하면서 차례대로 배열 요소를 이용할 수 있는 것이다.

3.1 문자열과 포인터에 대한 복습
 : 문자열 할당과 초기화를 되새기면서 열 번째 강의에서 설명했던 내용을 다시 한번 살펴보도록 하자. 문자열을 할당하고 초기화하는 한 가지 방법은 다음과 같이 char형 배열을 선언하는 것이다.

   char message[] = "This is the message.";

char형에 대한 포인터를 선언하여 문자열을 할당하고 초기화할 수도 있을 것이다.

   char *message = "This is the message.");

두 문장은 동이한 것이다. 어떤 문장을 사용하든지 컴파일러는 널 문자를 포함하는 문자열을 저장하기 위한 영역을 할당하며, 수식 message는 문자열의 시작 부분에 대한 포인터가 된다. 다음 두 문장은 어떤 뜻을 가지고 있는가?

   char message1[20];
   char *message2;

첫 번째 문장은 20자 길이의 char형 배열을 선언하고, message1은 배열의 첫 번째 요소에 대한 포인터이다. 배열의 저장 영역은 할당되었지만 배열이 초기화되지는 않았고 배열의 내용은 정해지지 않았다. 두 번째 문장은 char형에 대한 포인터 message2를 선언한다. 이 경우, 문자열을 저장하기 위한 영역이 할당되지 않고 단지 포인터를 저장하기 위한 공간만이 할당되어 있다. 만약 문자열을 생성하여 message2가 문자열을 지적하기 원한다면, 우선 문자열을 저장하기 위한 영역을 할당해야 한다. 열 번째 강의에서 이런 용도로 메모리 할당 함수 malloc()을 사용하는 방법을 배웠다. 모든 문자열을 저장하기 위해서는 컴파일 과정에서 미리 준비하거나 도는 프로그램 실행 과정에서 malloc()을 사용하여 필요한 공간을 할당해야 한다는 것을 기억하자.

3.2 char에 대한 포인터의 배열
 : 잠시동안 앞에서 배웠던 것을 복습했으므로 포인터의 배열을 선언해보자. 다음 문장은 char형에 대한 10개의 포인터를 가지는 포인터의 배열을 선언한다.

   char *message[10];

배열 message[]의 요소는 각각 char형에 대한 포인터이다. 짐작할 수 있듯이 배열을 선언할 대에는 문자열을 저장하기 위한 저장 영역을 할당하는 동시에 문자열을 초기화할 수 있다.

   char *message[10] = {"one, "two", "three"};

이 문장은 다음과 같은 동작을 수행한다.

·10개의 요소를 가지는 message라는 이름의 배열을 선언한다.  배열의 요소는 각각 char형에 대한 포인터이다.

·메모리 내에서 저장 영역을 할당하여 널 문자를 포함하는 초기화 문자열을 저장한다. 메모리의 정확한 위치는 신경 쓸 필요가 없다.

·message[0]은 첫 번째 문자열의 첫 번째 문자를 지적하고, message[1]은 두 번째 문자열의 첫 번째 문자를 지적하며, message[3]은 세 번째 문자열의 첫 번째 문자를 지적하도록 초기화된다.

이제 포인터의 배열을 사용하는 예제를 살펴보자.

<리스트 15.5> char형에 대한 포인터의 배열을 초기화하고 사용하는 프로그램

 /* char형에 대한 포인터의 배열 초기화 */


 #include <stdio.h>


 main()

 {

    char *message[8] = { "Four", "score", "and", "seven", "years", "ago,", "our", "forefathers"};


    int count;


    for(count = 0; count < 8; count++)

       printf("%s ", message[count]);

    printf"\n");

    return(0);

 }

=> <리스트 15.5>에 있는 프로그램은 char형에 대한 8개의 포인터를 가지는 배열을 선언하고, 7번째 줄과 8번째 줄에서는 배열 요소인 포인터가 8개의 문자열을 지적하도록 초기화하고 있다. 그리고 나서 11번째 줄과 12번째 줄은 배열의 각 요소를 화면 상에 출력하기 위해서 for문을 사용한다.

 여기에서는 포인터의 배열을 다루는 것이 문자열 자체를 다루는 것보다 쉽다는 사실을 알 수 있을 것이다. 이런 장점은 이 장에서 나중에 설명할 복잡한 프로그램에서 더욱 분명하게 드러난다. 또한, 나중에 설명할 것처럼 이런 장점은 함수를 사용할 때 가장 강력하다. 함수에서 문자열을 출력하기 위해서 여러 개의 문자열을 전달하는 것보다는 포인터의 배열을 전달하는 것이 더욱 쉽다. 문자열을 출력하기 위해 함수를 사용하도록 <리스트 15.5>에 있는 프로그램을 변경하여 포인터의 배열을 전달하는 것이 더 쉽다는 사실을 확인할 수 있다. <리스트 15.6>에는 변경된 프로그램이 나타나 있다.

<리스트 15.6> 포인터의 배열을 함수에 전달하기

 /* 함수에 포인터의 배열 전달하기 */


 #include <stdio.h>


 void print_strings(char *p[], int n);


 main()

 {

    char *message[8] = { "Four", "score", "and", "seven", "years", "ago,", "our", "forefathers"};


    printf_strings(message, 8);

    return(0);

 }


 void print_strings(char *p[], int n)

 {

    int count;


    for(count = 0; count < n; count++)

       printf("%s", p[count]);

    printf("\n");

 }

앞에서 포인터의 포인터를 설명할 때 나중에 예제를 설명할 것이라고 언급했던 것을 기억하는가? 앞의 예제가 바로 포인터의 포인터를 사용하는 예제이다. <리스트 15.6> 에서는 포인터의 배열을 선언했다. 배열의 이름은 첫 번째 요소에 대한 포인터이다. 함수에 배열을 전달할 때에는 포인터(배열의 첫번째 요소)에 대한 포인터 (배열 이름)를 전달하는 것이다.

3.3 예제
 : 이제 좀더 복잡한 예제를 살펴보자. <리스트 15.7>에 있는 프로그램은 포인터의 배열을 포함하여 지금까지 설명한 여러 가지 내용을 활용하고 있다. 프로그램은 문자열이 입력될 때 문자열을 저장하기 위한 영역을 할당하고, char형에 대한 포인터의 배열을 사용하여 저장 영역을 관리하며, 여러 줄의 입력을 키보드에서 받아들인다. 빈 줄을 입력하여 마지막이라는 것을 알려주면 프로그램은 입력된 내용을 알파벳 순으로 정렬하고 화면 상에 출력한다. 만약 이 프로그램을 처음부터 작성한다면 구조화 프로그래밍 방식으로 작성할 것이다. 우선, 프로그램이 수행해야 하는 동작을 목록으로 만들어보자.

① 빈 줄이 입력될 때까지 한 번에 한 줄씩 키보드에서 입력을 받아들인다.

② 입력된 내용을 알파벳 순으로 정렬한다.

③ 정렬된 내용을 화면 상에 출력한다.

이 목록에서 알 수 있듯이 프로그램은 적어도 세 개의 함수를 사용해야 한다. 하나는 입력을 받아들이고, 다른 하나는 입력된 내용을 정렬하며, 세 번째 함수는 입력된 내용을 출력한다. 이제, 독립적으로 각각의 함수를 작성할 수 있을 것이다. get_lines()라는 입력 함수는 무엇을 수행할 필요가 있을까? 다시, 목록을 만들어 보자.

① 입력된 문장의 수를 일단 모든 내용이 입력되면 함수를 호출한 프로그램으로 문장의 수를 돌려준다.

② 미리 설정되어 있는 문장의 수보다 많은 내용은 받아들이지 않는다.

③ 각각의 문장을 저장하기 위한 영역을 할당한다.

④ 문자열에 대한 포인터를 배열에 저장하여 모든 내용을 관리한다.

⑤ 빈 줄이 입력되면 원래의 프로그램으로 돌아간다.

이제, 알파벳 순으로 문장을 정렬하는 두 번째 함수에 대해서 생각해보자. 이 함수를 sort() 라고 하자. 여기서 사용되는 정렬 방식은 인접한 두 개의 문자열을 비교해서 두 번째 문자열이 첫 번째 문자열보다 작으면 서로 교환하는 아주 간단한 방법이다. 더욱 정확히 말하자면, 함수는 포인터의 배열 내에서 인접한 두 개의 문자열을 비교하여 필요하다면 포인터를 교환한다.

 문자열을 완전히 정렬하기 위해서는 배열의 처음부터 마지막까지 필요할 때마다 각 쌍의 문자열을 비교해야 한다. n개의 요소를 가지는 배열에서는 배열을 n - 1번 통과하며 비교해야 한다. 왜 n - 1번이나 배열을 통과해야 하는 것일까? 배열을 한 번 통과할 때 각각의 요소는 기껏해야 한 칸씩 이동될 수 있다. 예를 들면, 맨 앞에 위치되어야 하는 문자열이 실제로 끝에 위치되어 있다면 처음에는 배열을 통과하며 문자열을 맨 끝에서 앞으로 한 번 이동시키고, 다음에는 다시 한 번 앞으로 이동시키며, 계속해서 이런 과정을 반복해야 한다. 맨 끝에 위치되어 있는 문자열을 배열의 맨 앞으로 옮기려면 n - 1번 이동시켜야 한다. 이것은 아주 비효율적이고 좋지 못한 정렬 방식이라는 것을 기억하자. 그러나 예제 프로그램에서는 간단한 데이터를 정렬할 것이므로 이 방법이 사용하기 쉽고 이해하기 쉬우며 가장 적절하다. 마지막 함수는 화면 상에 정렬된 문자열을 출력한다. 사실, 이 함수는 이미 <리스트 15.6>에서 작성한 것을 약간 변경한 것이므로 <리스트 15.7>에서 사용하려면 약간 수정하면 된다.

<리스트 15.7> 키보드에서 여러 줄의 텍스트를 읽어들이고 알파벳순으로 정렬하여 출력하는 프로그램

 /* 키보드에서 문자열을 읽어들이고 정렬하여 화면에 출력한다. */


 #include <stdio.h>

 #include <stdlib.h>

 #include <string.h>


 #define MAXLINES 25


 int get_lines(char *lines[]);

 void sort(char *p[], int n);

 void print_strings(char *p[], int n);


 char *lines[MAXLINES];


 main()

 {

    int number_of_lines;


    /* 키보드에서 여러 줄을 읽어들인다. */


    number_of_lines = get_lines(lines);


    if(number_of_lines < 0)

    {

       puts("Memory allocation error");

       exit(-1);

    }


    sort(lines, number_of_lines);

    print_strings(lines, number_of_lines);

    return(0);

 }


 int get_lines9char *lines[])

 {

    int n = 0;

    char buffer[80];   /* 각 줄에 대한 임시 저장 공간 */


    puts("Enter one line at time; enter a blank when done.");


    while((n < MAXLINES) && (gets(buffer) != 0) &&

         (buffer[0] != '\0'))

    {

       if((lines[n] = (char *)malloc(strlen(buffer) + 1)) == NULL)

          return -1;

       strcpy(lines[n++], buffer);

    }

    return n;


 }   /* get_lines()의 끝 */


 void sort9char *p[], int n)

 {

    int a, b;

    char *x;


    for(a = 1; a < n; a++)

    {

       for(b = 0; b < n-1; b++)

       {

          if(strcmp(p[b], p[b+1]) > 0)

          {

             x = p[b];

             p[b] = p[b+1];

             p[b+1] = x;

          }

       }

    }

 }


 void print_strings(char *p[], int n)

 {

    int count;


    for(count = 0; count < n; count++)

       printf("\n%s ", p[count]);

 }

=> 이 프로그램을 상세히 살펴보면 도움이 될 것이다. 프로그램에서는 문자열을 다루기 위해서 여러 가지 새로운 라이브러리 함수를 사용하고 있다. 여기에서는 이런 함수들을 간단히 설명하고, 상세한 내용은 낭중에 "문자열 다루기"에서 설명하도록 하겠다. 이런 함수들을 사용하는 프로그램에는 헤더 파일 STRING.H가 포함되어야 한다.

get_lines() 함수에서 41번째 줄과 42번째 줄의 다음과 같은 while문은 입력을 제어한다. 한 줄로 살펴보자.

   while((n < MAXLINES) && (gets(buffer) != 0) && (vuffer[0] != '\0'))

while문에서는 세 가지 조건이 확인되고 있다. 첫 번째 조건인 n < MAXLINES는 지정된 수의 문장이 입력되지 않았는지 확인한다. 두 번째 조건인 get(buffer) != 0은 키보드에서 버퍼로 문장을 읽어들이기 위해서 라이브러리 함수 gets()를 호출하여 파일의 마지막에 도달하거나 또는 다른 어떤 에러가 발생하지 않았는지 확인한다. 세 번째 조건인 buffer[0] != '\0'는 방금 입력된 문장의 첫 문자가 널 문자가 아닌지 확인한다. 널 문자는 빈 줄이 입력되었다는 것을 알려주는 역할을 한다.

 세가지 조건의 어떤 것도 만족되지 않으면, while문은 종료되고 함수는 지금까지 입력된 문장의 수를 프로그램으로 돌려주며 제어를 전달한다. 만약 세가지 조건이 모두 참이라면 44번째 줄에 있는 다음과 같은 if문이 실행된다.

   if((lines[n] = (char *)malloc9strlin(buffer) + 1)) == NULL)

이 문장은 방금 입력된 문자열의 저장 영역을 할당하기 위해서 malloc()를 호출한다. strlen() 함수는 인수로 전달된 문자열의 길이를 돌려준다. 복귀값에는 malloc() 함수가 문자열과 함께 널 문자를 저장할 수 있는 영역을 할당하도록 하기 위해서 1이 더해진다. 앞에서 설명했던 malloc() 함수는 포인터를 돌려준다. 이 문장은 malloc()이 돌려주는 포인터의 값을 포인터의 배열에서 대응하는 요소에 할당한다. 만약 malloc()이 NULL을 돌려주면, if문에서는 함수를 호출한 프로그램으로 -1의 복귀값을 돌려준다. main()에서는 get_lines()의 복귀값을 확인하여 함수가 0보다 작은 값을 돌려주는지 확인한다. 23번째 줄부터 27번째 줄까지는 메모리 할당에러를 알려주고 프로그램을 종료한다. 메모리의 할당이 성공적이었다면 프로그램의 46번째 줄에서는 임시 저장 영역의 버퍼에서 방금 malloc()에 의해 할당된 저장 영역으로 문자열을 복사하기 위해 strcpy()함수를 사용한다. 그리고 나서 while문은 다른 문장을 읽어들이기 위해 전체 과정을 반복한다. 메모리 할당에러가 발생하지 않았다고 가정하고, 일단 get_lines()에서 main()으로 제어가 전달되면 다음과 같은 동작이 수행된 것이다.

·키보드에서 여러 줄의 텍스트가 입력되고 널 문자를 포함하는 문자열 형태로 메모리에 저장되었다.

·배열 lines[]는 각 문자열에 대한 포인터를 가진다. 배열 내의 포인터의 순서는 문자열이 입력된 순서이다.

·변수 number_of_lines는 입력된 문장의 수를 가진다.

이제, 문자열의 정렬 부분을 살펴보자. 프로그램에서는 실제로 문자열을 이동시키는 것이 아니라 단지 배열 lines[]에 포함되어 있는 포인터의 순서만을 이동시킨다는 것을 기억하자. 함수 sort()의 내용을 살펴보자. 57번째 줄부터 68번째 줄까지는 for문 내에 종속되어 있는 다른 하나의 for문이 사용되고 잇다. 바깥쪽의 순환문은 number_of_lines - 1번 실행된다. 바깥쪽의 순환문이 실행될 때마다 안쪽의 순환문은  n = 0에서부터 n = number_of_lines-1이 될 때까지 포인터의 배열에서 (string n)을 (string n + 1)과 비교한다. 실제 비교 동작은 두 개의 문자열에 대한 포인터를 바다아들이는 61번째 줄의 라이브러리 함수 strcmp()에 의해서 수행된다. 함수 strcmp()는 다음 중에서 하나의 값을 돌려준다.

·첫 번째 문자열이 두 번째 문자열보다 크다면 -> 0보다 큰 값

·두 문자열이 동일하다면 -> 0

·두 번째 문자열이 첫 번째 문자열보다 크다면 -> 0보다 작은 값 프로그램에서 strcmp()가 0보다 큰 값을 돌려준다면 첫 번째 문자열이 두 번째 문자열보다 '크다'는 것을 뜻하므로 문자열을 서로 교환해야 한다. 즉, lines[]내의 포인터를 교환해야 한다

이런 교환 동작은 임시 변수인 x를 사용하여 수행된다. 63번째 줄부터 65번째 줄까지는 실제 교환을 수행한다.

sort()에서 프로그램으로 제어가 전달될 때 lines[]내의 포인터는 순서대로 정렬된 상태가 된다. '가장 작은' 문자열에 대한 포인터는 lines[0]에 저장되고, 다음으로 '작은' 문자열에 대한 포인터는 lines[1]에 저장된다. 마지막으로, 프로그램은 화면 상에 정렬된 문자열을 출력하기 위해서 함수 print_strings()를 호출한다. 이 함수는 앞의 예제와 비슷하다. <리스트 15.7>에 있는 프로그램은 지금까지 여기서 사용한 것들 중에서 가장 복잡한 예제이다 이 프로그램은 지금까지 설명한 C 프로그래밍에 대한 많은 내용을 다루고 있다. 코드 분석을 참고로 해서 이 프로그램의 동작과 각 단계별 내용을 이해하기 바란다. 만약 이해할 수 없는 부분이 있다면, 다음 주제로 진행하기 전에 프로그램을 완전히 이해할 할 수 있을 때까지 여기서 관련된 내용을 다시 한 번 읽어보도록 하자.

4. 함수에 대한 포인터
: 함수에 대한 포인터는 함수를 호출하는 또다른 방법을 제공해준다. 여기서 아마도 '함수에 대한 포인터를 어떻게 구할 수 있지? 포인터는 변수가 저장된 주소값을 가지는 것이 아닌가?'라는 의문을 가질 것이다. 이 질문에 대한 해답은 양면성을 가지고 있다. 포인터가 주소값을 가진다는 것을 사실이지만 반드시 변수가 저장된 주소값일 필요는 없다. 프로그램이 실행될 때 각 함수의 코드는 특정 주소에서부터 시작하는 메모리 영역에 위치된다. 함수에 대한 포인터는 이렇게 메모리 영역에 저장된 함수의 시작 주소값을 가진다. 그렇다면 함수에 대한 포인터를 사용하는 이유는 무엇일까? 앞에서도 언급했듯이, 함수를 호출하는 방법에 많은 융통성을 제공해준다. 함수에 대한 포인터는 프로그램이 여러 가지 함수들 중에서 현재 상황에 적합한 것을 '선택하여' 실행하게 해준다.

4.1 함수에 대한 포인터 선언
: 다른 일반적인 포인터와 마찬가지로 함수에 대한 포인터를 사용하려면 먼저 선언해야 한다. 포인터는 다음의 형식을 사용한다.

   type (*ptr_to_func)(parameter_list);

이 문장은 type형을 돌려주고 parameter_list에 포함되어 있는 매개 변수를 받아들이는 함수에 대한 포인터 ptr_to_func를 선언한다. 다음은 몇 가지 구체적인 예이다.

   int (*func1)(int x);
   void (*func2)(double y, double z);
   char (*func3)(char *p[]);
   void (*func4)();

첫 번째 문장은 하나의 int형 인수를 받아들이고 int형 값을 돌려주는 함수에 대한 포인터 func1을 선언한다. 두 번째 문장은 두 개의 double형 인수를 받아들이고 void의 복귀형을 가지는, 즉 복귀값이 없는 함수에 대한 포인터 func2를 선언한다. 세 번째 문장은 chart형에 대한 포인터의 배열을 인수로 받아들이고 char형의 값을 돌려주는 함수에 대한 포인터 func3을 선언한다. 마지막 문장은 아무런 인수도 받아들이지 않고 void의 복귀형을 가지는 함수에 대한 포인터 func4를 선언한다. 포인터의 이름 주위에 괄호를 사용할 필요가 있을까? 첫 번째 예제의 경우 다음과 같은 문장을 사용할 수 없는가?

   int *func1(int x);

이 것은 간접 연산자인 *의 우선 순위에 관련된 문제이다. 간접 연산자는 매개 변수의 목록을 둘러싸고 있는 괄호보다 상대적으로 낮은 우선 순위를 가진다. 첫 번째 예제에서 괄호를 생략한 선언문은 func1을 int형에 대한 포인터를 돌려주는 함수로 선언한다. 포인터를 돌려주는 함수는 8번째 강의 "함수를 효율적으로 사용하는 방법"에서 다루어진다. 함수에 대한 포인터를 선언할 때에는 항상 포인터의 이름과 간접 연산자 주위에 괄호를 사용해야 한다는 사실을 기억하자. 그렇지 않으면 분명히 문제가 발생할 것이다.

4.2 함수에 대한 포인터의 초기화와 사용
: 함수에 대한 포인터를 사용하려면 포인터를 선언해야 할 뿐 아니라 어떤 것을 지적하도록 초기화해야 한다. 물론 여기서 '어떤 것'은 함수이다. 포인터가 지적해야 하는 함수에는 아무런 제한이 없다. 한가지 주의해야 할 사항이 있다면 함수의 복귀형과 매개 변수의 목록이 포인터를 선언할 때 지정된 복귀형이나 매개 변수의 목록과 일치해야 한다는 것이다. 예를 들어, 다음 문장은 함수와 함수에 대한 포인터를 선언하고 정의한다.

   float square(float x);    /* 함수 원형 */
   float (*p)(float x);       /* 포인터 선언 */
   float square(float x)     /* 함수 정의 부분 */
   {
      return x * x;
   }

함수 square()와 포인터 p는 동일한 매개 변수와 복귀 형태를 사용하므로 다음과 같이 p가 square를 지적하도록 초기화할 수 있다.

   p = square;

이제, 다음과 같이 포인터를 사용하여 함수를 호출할 수 있다.

  answer = p(x);

함수에 대한 포인터는 이처럼 간단한 것이다. 실제 사용 예는 <리스트 15.8>에 있는 프로그램 을 컴파일하고 실행하여 살펴보도록 하자. 이 프로그램은 함수에 대한 포인터를 선언하고 초기화한 후에, 처음에는 함수의 이름을 사용하고 그 다음에는 포인터를 사용하여 함수를 두 번 호출한다. 함수를 어떤 방법으로 호출하든지 결과는 같다.

<리스트 15.8> 함수를 호출하기 위해서 함수에 대한 포인터를 사용하는 프로그램  
 

 /* 함수에 대한 포인터 선언과 사용 예 */


 #include <stdio.h>


 /* 함수 원형 */


 double square(double x);


 /* 포인터 선언 */


 double (*p)(duble x);


 main()

 {

    /* p가 square()를 지적하도록 초기화 */


    p = square;


    /* square()를 두 가지 방법으로 호출 */

    printf("%f %f", square(6.6), p(6.6));

    return(0);

 }


 double square(double x)

 {

    return x * x;

 }


=> 7번째 줄에서는 square()를 선언한고 ,11번째 줄에서는 double형 인수를 받아들이고 double값 을 돌려주며 square()의 선언문과 일치하는 함수에 대한 포인터 p를 선언한다. 17번째 줄에 서는 포인터 p를 square로 설정한다. square나 p에서 괄호가 사용되지 않았다는 것에 주의하자. 20번째 줄은 square()와 p()의 호출에서 복귀되는 값을 출력한다.
괄호를 포함하지 않는 함수의 이름은 함수에 대한 포인터이다. 이것은 배열의 경우와 비슷하다. 함수에 대한 포인터를 선언하고 사용하는 것과 어떤 차이점이 있을까? 함수의 이름 자체는 포인터 상수로 변경이 불가능하다. 이 사실은 배열에서와 같다. 그러나 포인터 변수는 변경할 수 있다. 특히, 필요할 때마다 다른 함수를 지적하도록 설정할 수 있다.

<리스트 15.9>에 있는 프로그램은 함수에 정수형 값을 인수로 전달하여 함수를 호출한다. 전달되는 인수의 값에 따라 함수는 포인터가 세 가지 다른 함수의 하나를 지적하도록 초기화하고 나서 대응하는 마수를 호출하기 위해서 포인터를 사용한다. 이런 세 함수의 각각은 화면 상에 독특한 메시지를 출력한다.

<리스트 15.9> 상황에 따라 다른 함수를 호출하기 위해서 함수에 대한 포인터를 사용하는 프로그램

 /* 서로 다른 함수를 호출하기 위한 포인터의 사용 예 */


 #include <stdio.h>


 /* 함수 원형 */


 void func1(int x);

 void one(void);

 void two(void);

 void other(void);


 main()

 {

    int a;


    for(;;)

    {

       puts("\nEnter an integer between 1 and 10, 0 to exit: ");

       scanf("%d", &a);


       if(a == 0)

          break;

       func1(a);

    }

    return(0);

 }


 void func1(int x)

 {

    /* 함수에 대한 포인터 */


    void(*ptr)(void);


    if(x == 1)

       ptr = one;

    else if(x == 2)

       ptr = two;

    else

       ptr = other;


    ptr();

 };


 void one(void)

 {

    puts("You entered 1.");

 }


 void two(void)

 {

    puts("You entered 2.");

 }


 void other(void)

 {

    puts("You entered something other than 1 or 2.");

 }

=> 이 프로그램의 16번째 줄에서는 0의 값이 입력될 때가지 프로그램을 계속 실행하기 위해서 무한 루프를 사용하고 있다. 0이 아닌 값이 입력될 때 입력된 값은 func1()에 전달된다. func1()의 32번째 줄에 나타나 있는 함수에 대한 포인터 ptr의 선언문을 주의해서 살펴보자. 이 선언문은 ptr이 func1()에 대해서 지역 변수의 상태가 되도록 해주는데, 프로그램의 다른 부분에서는 이 포인터를 사용할 필요가 없으므로 이렇게 지역 변수로 선언하는 것이 좋다. func1()의 34번째 줄부터 39번째 줄까지는 입력된 값에 따라 ptr을 특정 함수로 설정한다. 41번째 줄은 특정 함수에 설정된 ptr()을 호출한다. 물론, <리스트 15.9>에 있는 프로그램은 예제로 사용하기 위한 것이다. 이 프로그램에서는 함수에 대한 포인터를 사용하지 않고도 쉽게 동일한 결과를 얻을 수 있을 것이다. 이제, 여러 가지 함수를 호출하기 위해서 함수의 인수로 포인터를 전달하여 사용하는 방법을 알아보도록 하자. <리스트 15.10>에 있는 프로그램은 <리스트 15.9>를 수정한 것이다.

<리스트 15.10> 함수에 대한 포인터를 인수로 전달하기

 /* 함수에 대한 포인터를 인수로 전달하기 */


 #include <stdio.h>


 /* 함수 원형. 함수 func1()은 어떤 인수도 받아들이지 않고 복귀값을 가지지 않는 함수에

    대한 포인터를 하나의 인수로 받아들인다. */



 void func1(void (*p)(void));

 void one(void);

 void two(void);

 void other(void);


 main()

 {

    /* 함수에 대한 포인터 */


    void (*ptr)(void);

    int a;


    for(;;)

    {

       puts("\nEnter an integer between 1 and 10, 0 to exit: ");

       scanf("%d", &a);


       if(a == 0)

          break;

       else if(a == 1)

          ptr = one;

       else if(a == 2)

          ptr = two;

       else

          ptr = other;

       func1(ptr);

    }

    return(0);

 }


 void func1(void (*p)(void)

 {

    p();

 }


 void one(void)

 {

    puts("You entered 1.");

 }


 void two(void)

 {

    puts("You entered 2.");

 }


 void other(void)

 {

    puts("You entered something other than 1 or 2.");

 }

=> <리스트 15.9>와 <리스트 15.10>의 차이점을 주의해서 살펴보자. 함수에 대한 포인터의 선언문은 main()의 18번째 줄로 이동되었다. 이제 main()의 26번째 줄부터 33번째 줄까지는 사용자가 입력한 값에 따라 포인터가 정확한 함수를 지적하도록 초기화하고 나서 초기화된 포인터를 func1()에 전달한다. <리스트 15.10>에서는 함수 func1()이 어떤 특별한 동작을 수행하지 않고 있다. 단지 ptr이 지적하는 함수를 호출한다. 이 프로그램도 또한 예제로 사용된 것이다. 여기서 설명하는 내용은 잠시 후에 설명할 것과 같은 실용적인 프로그램에 응용될 수 있다.

 함수에 대한 포인터를 사용하는 프로그래밍 작업의 한 가지 예는 정렬 작업이 필요한 경우이다. 프로그램에서는 가금 여러 가지 정렬 방식을 사용하기 원할 것이다. 예를 들어, 한 번은 알파벳 순서로 데이터를 정렬하고, 한 번은 알파벳의 역순으로 정렬하기 원할 수 있다. 함수에 대한 포인터를 사용하면 프로그램은 정확한 정렬 함수를 호출할 수 있다. 더욱 정확히 말하자면, 일반적으로 여러 가지 비교 함수를 호출하여 사용할 수 있는 것이다. <리스트 15.7>에 있는 프로그램을 다시 한 번 살펴보자. sort() 함수의 실제 정렬 순서는 라이브러리 함수 strcmp()가 돌려주는 값에 의해서 결정된다. 이 함수는 프로그램에서 사용되는 문자열이 다른 하나의 문자열보다 '작은지' 또는 '큰지'의 여부를 알려준다. 만약 A가 Z보다 작은 것으로 평가되는 알파벳 순서에 의해서 정렬 동작을 수행하는 하나의 함수와 Z가 Z보다 큰 것으로 평가되는 알파벳 역순에 의해서 정렬 동작을 수행하는 하나의 함수를 작성한다면 어떨까? 프로그램은 사용자에게 어떤 순서로 정렬하기 원하는지 결정하도록 요구하고, 정렬 함수는 포인터를 사용하여 적절한 비교 함수를 호출할 수 있다. <리스트 15.11>은 <리스트 15.7>에 있는 프로그램을 수정하고 이런 기능을 결합시킨 것이다.

<리스트 15.11> 여러 가지 정렬 방식을 사용하기 위해서 함수에 대한 포인터를 사용하는 프로그램

 /* 키보드에서 문자열을 읽어들이고 오름차순이나 내림차순으로 정렬하여 화면에

    출력한다. */


 #include <stdlib.h>

 #include <stdio.h>

 #include <string.h>


 #define MAXLINES 25


 int get_lines(char *lines[]);

 void sort(char *p[], int n, int sort_type);

 void print_strings(char *p[], int n);

 int alpha(char *p1, char *p2);

 int reverse(char *p1, char *p2);


 char *lines[MAXLINES];


 main()

 {

    int number_of_lines, sort_type;


    /* 키보드에서 여러 줄을 읽어들인다. */


    number_of_lines = get_lines(lines);


    if(number_of_lines < 0)

    {

       puts("Memory allocation error");

       esit(-1);

    }


    puts("Enter 0 for reverse order sort, 1 for alphabetical: ");

    scanf("%d", &sort_type);


    sort(lines, number_of_lines, sort_type);

    print_strings(lines, number_of_lines);

    return(0);

 }


 int get_lines(char *lines[])

 {

    int n = 0;

    char buffer[80];   /* 각 줄에 대한 임시 저장 공간 */


    puts("Enter one line at a time; enter a blank when done.");


    while(n < MAXLINES && gets(buffer) != 0 && buffer[0] != '\0')

    {

       if((lines[n] = (char *)malloc(strlen(buffer)+1)) == NULL)

          return -1;

       strcpy(lines[n++], buffer);

    }

    return n;


 }   /* get_lines()의 끝 */


 void sort(char *p[], int n, int sort_type)

 {

    int a, b;

    char *x;


    /* 함수에 대한 포인터 */


    int(*compare)(char *s1, char *s2);


    /* 포인터가 인수 sort_type에 따라 적절한 비교 함수를 지적하도록 초기화한다. */



    compare = (sort_type) ? reverse : alpha;


    for(a = 1; a < n; a++)

    {

       for(b = 0; b < n - 1; b++)

       {

          if(compare(p[b], p[b+1]) > 0)

          {

             x = p[b];

             p[b] = p[b+1];

             p{b+1] = x;

          }

       }

    }

 }   /* sort()의 끝 */


 void print_strings(char *p[], int n)

 {

    int count;


    for(count = 0; count < n; count++)

       printf("\n%s", p[count]);

 }


 int alpha(char *p1, char *p2)

 /* 알파벳 여순 비교 */

 {

    return(strcmp(p1, p2));

 }

=> main()의 32번째 줄과 33번째 줄은 어떤 정렬 방식을 사용하기 원하는지 묻는다. 선택된 정렬 순서는 sort_type에 저장된다. 이 값은 <리스트 15.7>에서 설명했던 다른 값과 함께 sort() 함수에 전달된다. sort() 함수는 약간 변경되었다. 64번째 줄은 두 개의 문자형 포인터(문자열)를 인수로 받아들이는 함수에 대한 포인터 compare()를 선언한다. 69번째 줄에서는 sort_type의 값을 기본으로 하여 compare()를 리스트에 추가된 두 가지 새로운 함수의 하나로 서정한다. 두 개의 새로운 함수는 alpha()와 reverse()이다. alpha()는 <리스트 15.7>에서 사용했던 것과 같은 라이브러리 함수 strcmp()를 사용한다. reverse()는 역순으로 정렬을 수행하기 위해서 전달된 매개 변수의 위치를 변경하여 사용한다.





 

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

13 장 고급 프로그램 제어문  (0) 2019.06.02
14장 화면, 프린터, 키보드 사용하기  (0) 2019.06.02
16장 링크리스트  (0) 2019.06.02
17장 디스크 파일의 사용  (0) 2019.06.02
API 윈도우 창 띄우기  (0) 2019.05.25

+ Recent posts