문자(character)는 하나의 문자(letter), 숫자(number), 구두점(punctuation mark), 또는 그 밖의 기호를 가리키고, 문자열(string)은 이런 문자들이 결합된 상태를 말한다. 문자열은 문자, 숫자, 구두점, 그 밖의 기호 등으로 구성되는 텍스트 데이터를 저장하는데 사용된다. 문자와 문자열은 많은 프로그램에서 매우 유용하게 사용된다. 오늘은 다음과 같은 내용을 배울 것이다.
·하나의 문자값을 저장하기 위한 C의 데이터형(char)을 사용하는 방법
·여러 개의 문자값으로 구성되는 문자열을 저장하기 위한 char형 배열의 생성
·문자와 문자열을 초기화하는 방법
·문자열에서 초기화하는 방법
·문자와 문자열을 입력하고 출력하는 방법

1. char 데이터형
: C는 문자값을 저장하기 위해서 char형을 사용한다. char형이 숫자형이라면 어떻게 문자값을 저장하기 위해서 사용할 수 있을까? 해답은 C가 문자를 저장하는 방법에 있다. 컴퓨터의 메모리에는 모든 데이터가 숫자 형태로 저장된다. 문자를 직접 저장하는 방법은 없다. 그러나 각각의 문자에 대해서는 대응하는 숫자 코드가 존재한다. 이런 숫자 코드를 ASCII(American Standard Code for Information Interchange) 코드나 ASCII 문자라고 한다. 대·소문자, 숫자, 구두점, 그 밖의 기호에는 모두 0과 255 사이의 ASCII 코드값이 할당되어 있다. 예를 들어, 문자 a에는 97이라는 ASCII 코드값이 할당되어 있다. char형의 변수에 문자를 저장할 때 실제로는 97의 값이 저장되는 것이다. char형에 저장할 수 있는 숫자의 범위는 표준 ASCII 코드의 범위와 일치하므로, char는 이론적으로 한 문자를 저장하는 데 적합하다. 이런 내용은 다소 혼란스러울 것이다. C에서 문자를 숫자 형태로 저장한다면 프로그램에서는 주어진 char형 변수가 문자를 저장하는지 숫자를 저장하는지 어떻게 알 수 있을까? 나중에 설명하겠지만, 문자값을 사용하기 위해서는 변수를 char형으로 선언하는 것만으로 충분하지 않다. 변수에 대해서 다른 어떤 동작을 수행할 필요가 있다.
·C프로그램에서 문자를 출력해야 하는 곳에서 char형의 변수가 사용되면 문자로 간주된다.
·C프로그램에서 숫자를 출력해야 하는 곳에서 char형의 변수가 사용되면 숫자로 간주된다. 이런 규칙은 C에서 문자값을 저장하기 위해서 숫자 데이터형을 사용할 수 있다는 사실을 확실히 증명한다.

좀더 상세한 내용을 살펴보도록 하자.

2. 문자 변수의 사용
: 다른 변수와 마찬가지로 문자 변수를 사용하기 위해서는 먼저 선언해야 하는데, 변수를 선언하는 동시에 초기화할 수도 있다. 다음은 몇 가지 예이다.

  char a, b, c;       /* 초기화되지 않는 세 개의 char형 변수 선언 */
  char code = 'x';   /* code라는 이름의 char형 변수를 선언하고 */
  …                 /* 문자 x를 저장한다. */
  code = '!';         /* code라는 이름의 변수에 !를 저장한다.

문자 그대로의 값을 가지는 실제 상수를 할당하기 위해서는 하나의 문자를 작은 따옴표 내에 포함시키면 된다. 컴파일러는 실제 상수를 주어진 값에 대응하는 ASCII 코드로 변환해서 숫자 코드값을 변수에 할당하게 된다.
#define 지시어나 const 키워드를 사용하면 char형의 기호 상수를 정의할 수도 있다.

  #define EX 'x'
  char code = 'EX';   /* x를 저장하는 것과 동일하다. */
  const char A - 'Z';

이제, 문자 변수를 선언하고 초기화하는 방법을 배웠으므로, 예제를 살펴보도록 하자. <리스트 10.1>에 있는 프로그램은 printf() 함수를 사용하여 문자가 숫자 형태로 저장된다는 사실을 보여준다. 함수 printf()는 문자와 숫자를 출력하는 데 사용될 수 있다. 형식화 문자열인 %c는 printf()가 문자를 출력하도록 지시하는 반면에, %d는 십진 정수형 값을 출력하도록 지시한다. <리스트 10.1>은 두 개의 char형 변수를 초기화하고 각각의 값을 출력한다. 처음의 값은 문자형으로 출력되고, 두 번째 값은 숫자형으로 출력된다.

<리스트 10.1> char형 변수의 숫자 특성을 보여주는 예

 /* char 변수의 숫자형 특성 */


 #include <stdio.h>


 /* 두 char형 변수 선언과 초기화 */


 char c1 = 'a';

 char c2 = 90;


 main(0

 {

    /* 변수 c1을 문자형으로 출력하고 숫자형으로 출력 */


    printf("\nAs a character, variable c1 is %c", c1);

    printf("\nAs a number, variable c1 is %d", c1);


    /* 변수 c2를 문자형으로 출력하고 숫자형으로 출력 */


    printf("\nAs a character, variable c2 is %c", c2);

    printf("\nAs a number, variable c2 is %d\n", c2);


    return 0;

 }

<리스트 10.2>에 있는 프로그램은 확장 ASCII 코드의 문자를 일부분 출력하는 것이다.

<리스트 10.2> 확장 ASCII 문자 출력하기

 /* 확장 ASCII 문자 출력 */


 #include <stdio.h>


 unsigned char x;   /* 확장 ASCII를 위해 부호 없는 형을 사용해야 함 */


 main()

 {

    /* 180부터 203까지의 확장 ASCII 문자 출력 */


    for(x = 180; x < 204; x++)

    {

       printf("\nASCII code %d is character %c", x, x);

    }

    return 0;

 }

3. 문자열의 사용
: char형 변수는 단지 하나의 문자값만을 저장할 수 있으므로 유용성은 제한적이다. 프로그램을 작성할 때에는 일련의 문자들로 구성되는 문자열(strings)을 저장하는 방법이 필요할 것이다. 사람의 이름이나 주소 등은 문자열의 예이다. 문자열을 저장하기 위한 특정 데이터형이 존재하는 것은 아니지만, C에서는 문자형 배열을 통해서 문자열을 다루게 된다.

3.1 문자형 배열
: 예를 들어, 6개의 문자를 가지는 문자열을 저장하기 위해서는 7개의 요소를 가지는 char형 배열을 선언할 필요가 있다. char형 배열은 다른 데이터형의 배열과 동일한 방법으로 선언된다. 예를 들어, 다음 문장은

  char string[10];

10개의 요소를 가지는 char형 배열을 선언한다. 이 배열은 9개의 이하의 문자로 구성되는 문자열을 저장하는 데 사용할 수 있다. 그러나 10개의 요소를 가지는 배열이 왜 9개 이하의 문자만을 저장할 수 있는지에 대해서 의문을 가질 것이다. C에서 문자열에는 일련의 문자들 외에 \0으로 표현되는 특수한 널(null) 문자가 추가된다. 널 문자는 백슬래시와 숫자 0의 두 문자로 표현되지만 실제로는 한 문자로 간주되며, ASCII 코드에서 0의 값을 가지고 있다. 그래서 C 프로그램이 문자열 Alabama를 저장할 때 실제로는 7개의 문자인 A, l, a, b, a, m, a 외에도 널 문자인 \0을 포함하고 있으므로, 전체적으로는 8개의 문자를 저장한다. 결과적으로 문자 배열에는 배열 요소의 개수보다 한 문자 적은 문자열을 저장할 수 있는 것이다.

char형 변수는 1바이트의 크기를 가지므로 char형 변수의 배열에서 사용되는 바이트 수는 배열 내의 요소의 개수와 동일하다.

3.2 문자 배열의 초기화
: C의 다른 데이터형에서와 마찬가지로 문자 배열은 선언되는 동시에 초기화될 수 있다. 문자 배열에는 다음과 같이 각각의 요소에 값을 할당할 수 있다.

  char string[10] = {'A', 'l', 'a', 'b', 'a', 'm', 'a','\0'};

그러나 일련의 문자들로 구성되는 실제 문자열(literal string)을 큰 따옴표 내에 포함시켜서 사용하는 것이 더욱 편리하다.

  char string[10] = "Alabama";

프로그램 내에서 문자열을 사용할 때 컴파일러는 문자열의 마지막 부분에 자동으로 널 문자를 추가한다. 배열을 선언할 때 첨자의 수를 지정하지 않는다면 컴파일러는 배열의 크기를 자동으로 계산해준다. 그래서 다음 문장은 8개의 요소를 가지는 배열을 선언하고 초기화한다.

  char string[] = "Alabama";

문자열에는 마지막을 뜻하는 널 문자가 반드시 포함되어야 한다는 것을 기억하기 바란다. 함수가 문자열의 마지막 부분을 인식하는 다른 방법은 전혀 없다. 만약 널 문자가 생략되면, 프로그램은 문자열이 메모리에서 널 문자가 나타나는 곳까지 확장되어 있다고 간주할 것이다. 이런 프로그래밍 에러는 다루기 힘들다....

4. 문자열과 포인터
: 앞에서는 문자열이 마지막을 표시하는 널 문자를 가지고 있으며 char형 배열에 저장된다는 것에 대해서 설명했다. 물론, 문자열이 배열에 저장될 때에는 전체 배열을 차지하지 않을 수도 있다. 문자열의 마지막은 널 문자에 의해서 표시된다는 것을 배웠으므로, 이제 문자열의 시작을 표시하는 것은 무엇인지 알 필요가 있을 것이다. 문자열의 시작 부분은 어떤 것으로 표시되는 것이 아니라 지적(point)된다고 보는 것이 더욱 정확할 것이다. 이런 내용은 약간의 힌트를 제공한다. 앞서 배운 "포인터에 대해서"에서는 배열의 이름이 첫 번째 요소에 대한 포인터라는 것을 설명했다. 그래서 배열에 저장된 문자열을 다루기 위해서는 배열의 이름을 사용하면 된다. 실제로 C에서는 배열의 이름을 통해서 문자열을 사용하는 것이 가장 일반적인 방법이다. 구체적으로 말하자면, 문자열을 다루기 위해서 배열의 이름을 사용하는 것은 C의 라이브러리 함수에서 사용되는 방법이다. C의 표준 라이브러리에는 문자열을 다루는 여러 가지 함수가 포함되어 있다. 문자열을 다루는 함수의 하나에 문자열을 전달하기 위해서는 배열의 이름을 전달하면 된다. 예를 들어, 문자열 출력 함수인 printf()와 이 장의 후반부에서 설명할 puts()에도 배열의 이름을 전달하면 된다.

지금까지는 '배열에 저장되는 문자열'에 대해서 설명했다. 그렇다면 문자열을 배열에 저장하지 않고 사용할 수 있을까? 이런 방법에 대해서 알아보도록 하자.

5. 배열에 저장되지 않는 문자열
: 앞에서는 문자열이 문자형 배열의 이름과 널 문자로 정의된다고 설명했다. 배열의 이름은 문자열의 싲가을 가리키는 char형의 포인터이다. 널 문자는 문자열의 마지막을 가리킨다. 배열에서 문자열이 차지하는 실제 공간은 이런 사항들을 통해서 확인해야 한다. 실제로 배열을 사용하는 유일한 목적은 문자열을 위한 공간을 할당해서 제공하는 것이다. 만약, 배열을 사용하여 저장 영역을 할당하지 않고도 필요한 메모리 영역을 얻을 수 있다면 어떨까? 어떤 방법을 사용하든지 마지막에 널 문자를 포함하는 문자열을 저장할 수는 있다. 또한, 배열을 통해서 할당된 메모리에 문자열을 저장할 때와 마찬가지로 첫 본째 문자에 대한 포인터를 사용하여 문자열의 시작 부분을 지적할 수 있을 것이다. 그렇다면 어떻게 메모리 저장 영역을 할당할 수 있을까? 두 가지 방법이 있는데, 하나는 프로그램이 컴파일될 때 문자열을 저장하기 위한 영역을 할당하는 것이고, 다른 하나는 동적 메모리 할당이라고 하며 프로그램이 실행되는 동안 메모리 영역을 할당하기 위해서 malloc() 함수를 사용하는 것이다.

5.1 컴파일시에 문자열의 저장 영역을 할당하는 방법
: 앞에서도 설명했듯이 문자열의 시작 부분은 char형 변수에 대한 포인터에 의해서 지적된다. 포인터를 선언하는 방법은 다음과 같다.

  char *message;

이 문장은 message라는 이름의 char형 변수에 대한 포인터를 선언한다. 지금은 이 포인터가 어떤 것도 지적하지 않지만 다음과 같은 문장을 사용하여 포인터를 선언한다면 어떻게 될까?

  char *message = "Great Caesar\'s Ghost!";

이것은 문자열의 마지막을 표시하는 널 문자를 가지는 문자열 'Great Caesar's Ghost!를 메모리 내의 특정 영역에 저장하고, 포인터 message는 문자열의 첫번째 문자를 지적하도록 초기화된다. 메모리 내에서 문자열이 저장되는 부분에 대해서는 신경 쓸 필요가 없다. 컴파일러에 의해서 자동으로 다뤄지기 때문이다. 일단 message를 정의하고 나면 문자열에 대한 포인터로 사용할 수 있다.
앞의 선언과 초기화는 다음과 같은데, 두 가지 표기인 *message와 message[]는 모두 '문자열에 대한 포인터'를 뜻한다.

  char message[] = 'Great Caesar\'s Ghost!";

이렇게 문자열의 저장 영역을 할당하는 방법은 프로그램을 작성하는 동안 필요한 문자열을 알고 있을 때 유용하다. 그러나 프로그램을 작성할 때 미리 알 수 없는 사용자의 입력 동작 이나 다른 어떤 동작에 의해서 내용이 바뀌는 문자열을 저장할 필요가 있을 때에는 어떻게 해야 할까? 이 때에는 '필요에 따라' 저장 영역을 할당해주는 malloc() 함수를 사용해야 한다.

5.2 malloc() 함수
: malloc() 은 C에서 제공되는 메모리 할당(memory allocation) 함수의 하나이다. malloc()을 호출할 때에는 필요한 메모리 양(바이트 단위)을 전달해야 한다. malloc()은 필요한 만큼의 메모리 블록을 확보해서 보존하고 블록의 첫 번째 바이트의 주소값을 돌려준다. 메모리의 위치에 대해서는 신경 쓸 필요가 없다. 자동으로 다뤄진다.

malloc() 함수는 메모리의 주소값을 돌려주고, 함수의 복귀형은 void형에 대한 포인터이다. 왜 void형에 대한 포인터를 돌려주는 것일까? void형에 대한 포인터는 모든 데이터형과 호환성이 있다. malloc()함수에 의해서 할당되는 메모리는 C의 어떤 데이터형을 저장하기 위해서도 사용될 수 있어야 하므로 void형이 가장 적합하다.


5.3 malloc() 함수 사용하기
: 하나의 char형 변수의 값을 저장하는 메모리를 할당하기 위해서 malloc()을 사용할 수도 있다. 우선, char형에 대한 포인터를 선언한다.

  char *ptr;

다음으로 malloc()을 호출하고 원하는 메모리 블록의 크기를 전달한다. char형이 1바이트를 차지하므로 1바이트의 메모리 블록이 필요할 것이다. malloc()이 돌려주는 복귀값은 포인터에 할당된다.

  ptr = malloc(1);

이 문장은 1바이트의 메모리 블록을 할당하고 주소값을 ptr에 저장한다. 프로그램에서 선언되는 변수와 달리 이런 메모리 영역은 이름을 가지지 않는다. 단지 포인터를 통해서 메모리 영역을 사용할 수 있다. 예를 들어, 문자 'x'를 할당된 메모리에 저장하기 위해서는 다음을 사용할 것이다.

  *ptr = 'x';

malloc()을 사용하여 문자열을 저장하기 위한 영역을 할당하는 것은 char형의 변수값을 저장하기 위한 영역을 할당하기 위해서 malloc()을 사용하는 것과 거의 비슷하다. 중요한 차이점이 있다면 필요한 메모리의 양을 알 필요가 있다는 것이다. 중요한 차이점이 있다면 필요한 메모리의 양을 알 필요가 있다는 것이다. 즉, 문자열에 포함되어 있는 문자의 개수를 알아야 한다. 이런 필요량은 프로그램에 따라 달라진다. 여기에서는 99개의 문자를 가지는 문자열과 마지막을 표시하는 널 문자를 합하여 전체적으로 100개의 문자를 저장하는 영역을 할당하기 원한다고 하자. 우선, char형에 대한 포인터를 선언하고 나서 malloc()을 호출한다.

  char *ptr;
  ptr = malloc(100);

이제, ptr은 100개의 문자를 가지는 문자열을 저장하기 위해서 사용할 수 있는 100바이트의 할당된 메모리 블록을 지적한다. 이제 다음과 같은 배열 선언문을 통해서 메모리를 할당한 것처럼 ptr을 사용할 수 있을 것이다.

  char ptr[100];

malloc() 함수는 프로그램에서 필요한 만큼의 메모리를 즉시 할당할 수 있게 해준다. 물론, 사용할 수 있는 메모리의 양에는 제한이 있는데, 실제 값은 컴퓨터에 설치된 메모리의 양과 프로그램에서 사용하는 메모리의 양에 따라 달라진다. 충분한 메모리가 남아 있지 않다면 malloc()은 널(0)을 돌려준다. 프로그램에서는 항상 malloc()의 복귀값을 확인하여 요구한 메모리가 성공적으로 할당되었는지 알아보아야 한다. 메모리 할당이 실패하면 malloc()은 복귀값으로 STDLIB.H에 정의되어 있는 기호 상수 NULL을 돌려준다.
<리스트 10.3>은 malloc()의 사용 예를 보여준다. malloc()을 사용하는 모든 프로그램에는 헤더 파일 STDLIB.H가 포함되어야 한다.

<리스트 10.3> 문자열 데이터를 저장할 영역을 할당하기 위해 malloc() 함수를 사용하는 예

 /* 문자열 데이터를 위한 공간을 할당하기 위해 malloc()을 사용하는 예 */


 #include <stdio.h>

 #include <stdlib.h>


 char count, *ptr, *p;


 main()

 {

    /* 35바이트의 블록 할당. 성공 여부를 확인한다. */

    /* exit() 라이브러리 함수는 프로그램을 마친다. */


    ptr = malloc(35 * sizeof(char));


    if(ptr == NULL)

    {

       puts("Memory allocation error.");

       exit(1);

    }


    /* A ~ Z의 ASCII 코드인 65부터 90까지의 값으로 문자열을 채운다. */

    /* p는 문자열을 진행하는 데 사용되는 포인터이다. */

    /* ptr이 문자열의 시작을 지적하게 한다. */


    p = ptr;


    for(count = 65; count < 91; count++)

       *p++ = count;


    /* 종료 널 문자를 추가한다. */


    *p = '\0';


    /* 화면에 문자열을 출력한다. */


    puts(ptr);


    return 0;

 }

-> 출력
  ABCDEFGHIJKLMNOPQRSTUVWXYZ

=> malloc()이 항상 요구한 메모리 블록을 정상적으로 할당하지는 않는다는 사실을 기억하기 바란다. 실제로는 malloc() 함수에게 메모리를 보존하도록 '지시'하는 것이 아니라 메모리를 할당하도록 '요청'하는 것이다.

6. 문자와 문자열의 출력
 프로그램에서 문자열 데이터를 사용한다면 가끔 문자열을 화면 상에 출력할 필요가 있을 것이다. 문자열은 대개 puts()나 printf() 함수를 사용하여 출력된다.

6.1 puts() 함수
: 앞에서는 이미 여러 프로그램에서 라이브러리 함수 puts()의 사용 예를 보았다. puts() 함수는 문자열을 화면 상에 출력한다. 함수의 이름(put string)도 이런 기능을 반영한 것이다. put()는 출력되는 문자열에 대한 포인터를 인수로 받아들인다. 실제 상수(literal constant)는 문자열에 대한 포인터이므로 puts()는 변수에 저장된 문자열 뿐 아니라 실제 상수로 정의된 문자열을 출력하기 위해서도 사용될 수 있다. puts() 함수는 출력할 각 문자열의 마지막에 자동으로 문자 진행(newline) 문자를 추가하므로, puts()를 사용하여 출력되는 문자열은 각각 한 줄을 차지한다. <리스트 10.4>에 있는 프로그램은 puts()의 사용 예를 보여준다.

<리스트 10.4> 텍스트를 화면에 출력하기 위한 puts() 함수의 사용 예

 /* puts()를 사용하여 문자열을 출력하는 예 */


 #include <stdio.h>


 char *message1 = "c"

 char *message2 = "is the"

 char *message3 = "best"

 char *message4 = "programming"

 char *message5 = "language!!"


 main()

 {

    puts(message1);

    puts(message2);

    puts(message3);

    puts(message4);

    puts(message5);


    return 0;

 }

-> 출력
 C
 is the
 best
 programming
 language!!

6.2 printf() 함수
: 또한, 라이브러리 함수 printf()를 사용하여 문자열을 출력할 수도 있다. 7번째 강의에서 printf()가 데이터의 출력 형태를 형식화하기 위해서 형식화 문자열(format string)과 변환 문자(conversion specifier)를 사용한다는 것에 대해서 설명했다. 문자열을 출력하기 위해서 변환 분자 %s를 사용하자. printf()의 형식화 문자열에서 %s가 사용되면 함수는 인수 목록에서 대응하는 인수를 %s에 일치시킨다. 문자열의 경우 이런 인수는 출력하기 원하는 문자열에 대한 포인터가 되어야 한다. printf() 함수는 문자열의 마지막을 뜻하는 널 문자가 나타날 때까지 문자열을 화면 상에 출력한다. 예를 들어, 다음과 같은 문장이 있다.

  char *str = "A message to display";
  printf("%s", str);

printf()에서는 여러 개의 문자열과 숫자 변수를 일반적인 텍스트 메시지와 섞어서 출력할 수 있다.

  char *bank = "First Federal";
  char *name = "John Doe";
  int balance = 1000;
  printf("The balance at %s for %s is %d.", bank, name, balance);

다음과 같은 결과가 출력된다.

  The balance at First Federal for John Doe is 1000.

지금까지 설명한 내용을 이해한다면 프로그램 내에서 문자열 데이터를 출력할 수 있다.

7. 키보드에서 문자열을 읽어들이는 방법
: 프로그램은 문자열을 출력하는 것 외에도 가끔 키보드를 통해서 입력되는 문자열 데이터를 받아들일 필요가 있다. C 라이브러리는 이런 입력 동작을 수행하기 위해서 사용할 수 있는 두 함수 gets()와 scanf()를 제공한다. 그러나 키보드에서 문자열을 읽어들이기 위해서는 우선 문자열을 저장하기 위한 영역을 확보해야 한다. 문자열을 저장하기 위한 영역은 앞에서 설명한 배열이나 malloc() 함수를 사용하여 확보할 수 있다.

7.1 gets() 함수를 사용하여 문자열을 입력하는 방법
: gets() 함수는 키보드에서 문자열을 읽어들인다. gets() 함수는 Enter키를 눌러서 문자 진행(newline) 문자를 입력할 때까지 키보드에서 입력되는 모든 문자를 읽어들인다. 함수는 마지막의 문자 진행 문자를 제고하고 널 문자를 추가하여 처음에 함수를 호출했던 프로그램으로 문자열을 돌려준다. 이렇게 읽어들인 문자열은 char형에 대한 포인터가 가리키는 메모리 영역에 저장된다. gets()를 사용하는 프로그램은 STDIO.H 파일을 포함해야 한다. <리스트 105>는 함수의 사용 예를 보여 준다.

<리스트 10.5> 키보드에서 문자열 데이터를 입력하기 위해 gets()를 사용하는 프로그램

 /* gets() 라이브러리 함수의 사용 예 */


 #include <stdio.h>


 /* 입력값을 저장하기 위한 문자형 배열 할당 */


 char input[81];


 main()

 {

    puts("Enter some text, then press Enter: ");

    gets(input);

    printf("You entered: %s\n", input);


    return 0;

 }

-> 출력
  Enter some text, then press Enter:
  This is a test
  You entered: This is a test

이 예제에서는 사용되지 않았지만  gets() 함수는 복귀값을 가지고 있다. gets()는 입력 문자열이 저장되는 주소값인 char형에 대한 포인터를 돌려준다. 이것은 gets()에 전달되는 것과 동일한 값이지만, 함수는 프로그램으로 복귀값을 돌려주어 빈 줄이 입력되었는지 확인할 수 있게 해준다.<리스트 10.6>에는 이 복귀값을 사용하는 예가 나타나 있다.

<리스트 10.6> 빈 줄이 입력되었는지 확인하기 위해서 gets()의 복귀값을 사용하는 프로그램

 /* gets()의 복귀값을 사용하는 예 */


 #include <stdio.h>


 /* 입력값을 저장하기 위한 문자형 배열과 포인터 선언 */


 char input[81], *ptr;


 main()

 {

    /* 안내문 출력 */


    puts("Enter text a line a time, then press Enter.");

    puts("Enter a blank line when done.");


    /* 입력이 빈줄이라면 계속 순환 */


    while(*(ptr = gets(input)) != NULL)

       printf("You entered %s\n", input);


    puts("Thank you and goodbye");


    return 0;

 }

<리스트 10.6>의 while문에서 이런 비교 동작을 수행한다. 이것은 다소 복잡한 것이므로 순서대로 주의해서 살펴보도록 하자. <그림 10.1>은 내용을 쉽게 이해할 수 있도록 도와주기 위해서 문장을 구성 요소별로 구분한 것이다.


<그림 10.1> 빈 줄이 입력되었는지 확인하는 while문의 구성 요소

① gets() 함수는 문자 진행(newline) 문자가 입력될 때가지 키보드에서 입력되는 내용을 받아들인다.

② 문자 진행 문자를 제거하고 널 문자를 추가한 상태의 입력 문자열은 input이 지적하는 메모리 영역에 저장된다.

③ 포인터 ptr에는 input과 동일한 값인 문자열의 주소가 저장된다.

④ 할당문의 결과는 등호의 왼쪽 부분에 있는 변수의 값에 의해서 결정된다. 그래서 수식 ptr = gets(input)는 ptr이 가지는 값을 뜻한다. 이 수식을 괄호 내에 포함시키고 수식의 앞부분에 간접 연산자(*)를 추가하면, 전체적인 수식은 ptr이 지적하는 주소에 저장되는 값을 뜻하게 된다. 물론, 이것은 입력 문자열의 첫 번째 문자를 뜻하는 것이다.

⑤ NULL은 헤더 파일 STDIO.H에 정의되어 있는 기호 상수로 널 문자(0)와 같은 값을 가진다.

⑥ 입력 문자열의 첫 번째 문자가 널 문자가 아니라면, 즉 빈줄이 입력되지 않았다면 비교문은 참으로 평가되고 while문이 실행된다. 만약 첫 번째 문자가 널 문자라면, 즉 빈 줄이 입력되었다면 비교문은 거짓으로 평가되고 while문은 종료된다.

포인터를 사용하여 데이터를 저장하는 gets()나 다른 어떤 함수를 사용할 때에는 포인터가 할당된 메모리 영역을 분명히 지적하도록 해주어야 한다. 다음과 같은 실수를 저지르는 것은 흔한 일이다.

   char *ptr;
   gets(ptr);

포인터 ptr은 선언되었지만 초기화되지 않았다. ptr은 어떤 영역을 지적하고 있지만 정확히 어느 곳인지 알 수는 없다. gets() 함수는 정확한 주소를 알지 못하는 상태에서 실행되므로 입력 문자열을 ptr이 지적하는 임의의 주소에 저장한다. 저장되는 입력 문자열은 프로그램이나 운영체제와 같은 중요한 내용을 대치할 수 있다. 컴파일러는 이런 문제를 찾아주지 않으므로, 프로그램을 작성할 때에는 이렇게 초기화되지 않은 포인터의 사용에 주의할 필요가 있다.

7.2 scanf() 함수를 사용하여 문자열을 입력하는 방법
: scanf() 라이브러리 함수가 키보드에서 숫자 데이터 값을 읽어들인다는 것을 설명했다. 이 함수는 문자열을 입력하는 경우에도 사용할 수 있다. scanf()는 입력되는 내용을 받아들이는 방법을 결정하기 위해서 형식화 문자열(format string)을 사용한다는 사실을 기억하자. 문자열을 읽어들이기 위해서는 scanf()으 형식화 문자열에 변환 문자 %s를 포함시켜야 한다. gets()와 마찬가지로 scanf()에는 문자열의 저장 영역에 대한 포인터가 전달된다.

scanf()는 문자열의 시작과 끚을 어떻게 구분할까? 문자열은 공백이 아닌 첫 번째 문자에서 부터 시작된다. 문자열의 마지막은 두 가지 방법으로 결정된다. 형식화 문자열에 %s를 포함시키면 문자열의 입력은 빈칸, 탭, 문자 진행(newline) 문자와 같은 공백이 입력될 때 끝나고 공백은 문자열에 포함되지 않는다. 형식화 문자열에 %ns를 포함시키면 scanf()는 n개의 문자가 입력되거나 또는 공백이 입력될 때가지 문자열을 읽어들인다. 여기서 n은 문자의 개수를 나타낸다. 또한, 형식화 문자열에 하나 이상의 %s를 포함시키면 하나의 scanf()를 사용하여 여러 개의 문자열을 입력할 수도 있다. 방금 설명했던 문자열의 입력에 대한 규칙은 형식화 문자열에 포함되어 있는 각각의 %s에 대해서 적용된다. 예를 들어, 다음과 같은 문장을 실행하고,

   scanf("%s%s%s", s1, s2, s3);

이 문장에 대응하여 January  February  March를 입력하면 문자열 s1에는 January가 할당되고, s2에는 February가 할당되며, s3에는 March가 할당된다. 문자열의 길이를 지정하는 경우에는 어떨까 ? 만약 다음과 같은 문장을 실행하고

   scanf("%3s%3s%3s", s1, s2, s3);

September를 입력하면 s1에는 Sep이 할당되고, s2에는 tem이 할당되며, s3에는 ber가 할당된다. scanf() 함수가 요구하는 것보다 적은 문자열을 입력하거나 많은 문자열을 입력하면 어떻게 될까? 요구하는 것보다 적은 개수의 문자열을 입력하면 scanf()는 부족한 내용을 받아들이기 위해서 계속 대기할 것이고, 프로그램은 나머지 문자열이 입력될 때까지 진행되지 않을 것이다. 예를 들어, 다음 문장을 실행하고

   scanf(%s%s%s", s1, s2, s3);

January February를 입력하면 프로그램은 scanf()의 형식화 문자열에서 지정된 세 번째 문자열을 입력하도록 요구할 것이다. 또한, 요구되는 것보다 많은 개수의 문자열을 입력하면 필요하지 않은 문자열은 키보드 버퍼 내에 남아 있게 되고, 나중에 scanf()나 다른 어떤 문자열 입력문이 실행될 때 읽어들여지게 된다. 예를 들어, 다음과 같은 문장을 실행하고

   scanf("%s%s", s1, s2);
   scanf("%s", s3);

January February March를 입력하면 문자열 s1에는 January가 할당되고, s2에는 February가 할당되며, s3에는 March가 할당된다.

scanf() 함수는 성공적으로 입력된 데이터의 수에 해당하는 정수값을 복귀값으로 가진다. 이 복귀값은 대개 무시된다. 텍스트만을 입력할 때에는 scanf()보다 gets() 함수가 더 많이 사용된다. scanf() 함수는 텍스트와 숫자 데이터를 함께 입력하는 경우에 유용하게 사용된다. <리스트 10.7>에 있는 프로그램은 이런 함수들의 사용 예를 보여준다. 7번째 강의에서 설명했듯이 scanf()에서 숫자 변수의 값을 읽어들일 때에는 주소 연산자(&)를 사욜해야 한다는 것을 기억하기 바란다.

<리스트 10.7> 숫자와 텍스트 데이터를 입력하기 위해서 scanf()를 사용하는 프로그램

 /* 숫자와 텍스트 데이터를 입력하기 위한 scanf()의 사용 예 */


 #include <stdio.h>


 charlname[81], fname[81];

 int count, id_num;


 main()

 {

    /* 사용자에게 안내 */


    puts("Enter last name, first name, ID number separated");

    puts("by spaces, then press Enter.");


    /* 세 데이터 항목 입력 */


    count = scanf("%s%s%d", lname, fname, &id_num);


    /* 데이터 출력 */


    printf("%d items entered" %s %s %d \n", count, fname, lname, id_num);


    return 0;

 }

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

8장 숫자 배열 사용하기  (0) 2019.06.02
9장 포인터에 대해서  (0) 2019.06.02
11장 구조체  (0) 2019.06.02
12장 변수의 범위  (0) 2019.06.02
13 장 고급 프로그램 제어문  (0) 2019.06.02

많은 프로그래밍 작업은 구조체(structures)라고 하는 C의 데이터형을 통해서 단순화될 수 있다. 구조체는 프로그램에서 필요한 데이터형을 프로그래머가 직접 구성하여 사용하는 데이터 저장 방식의 한 가지이다. 오늘은 다음과 같은 내용을 배운다.

·단순 구조체와 복합 구조체에 대해서
·구조체를 정의하고 선언하는 방법
·구조체에 포함되어 있는 데이터를 사용하는 방법
·배열을 포함하는 구조체와 구조체의 배열을 생성하는 방법
·구조체 내에서 포인터를 선언하는 방법과 구조체에 대한 포인터를 선언하는 방법
·함수의 인수로 구조체를 전달하는 방법
·공용체를 정의하고, 선언하고, 사용하는 방법
·구조체로 새로운 데이터형을 정의하는 방법

1. 단순 구조체
 : 구조체(structure)는 여러 개의 변수를 쉽게 사용할 수 있도록 하나의 이름으로 묶은 하나 이상의 변수의 집합이다. 구조체에 포함되는 변수는 배열에서와는 달리 여러 가지 데이터형 이 될 수 있다. 구조체는 배열이나 다른 구조체 등 C의 모든 데이터형을 포함할 수 있다. 구조체에 포함되는 각각의 변수를 구조체 멤버(member)라고 한다. 우선, 단순 구조체에 대해서 알아보도록 하자. 이렇게 구조체를 두 가지 형태로 구분하여 설명하는 것이 이해하기 쉽지만, 실제로 C 언어에서는 단순 구조체와 복합 구조체를 구분해서 사용하지 않는다는 것을 기억하기 바란다.

1.1 구조체의 정의와 선언
 : 만약 그래픽 프로그램을 작성한다면, 프로그램은 화면 상에 출력되는 점의 좌표를 다룰 필요가 있을 것이다. 화면 좌표는 수평 좌표를 나타내는 x값과 수직 좌표를 나타내는 y값으로 구성된다. 다음과 같이 화면에서 점의 위치를 x와 y값으로 나타내는 coord라는 이름의 구조체를 정의할 수 있다.

  struct coord {
     int x;
     int y;
  };

구조체 정의가 시작된다는 것을 알려주는 struct 키워드 다음에는 반드시 구조체의 이름이나 태그(tag)가 포함되어야 한다. 이것은 C에서 다른 변수를 선언할 때와 같다. 구조체 이름 뒤에 있는 중괄호에는 구조체 멤버인 변수의 목록이 포함된다. 각각의 구조체 멤버에 대해서 변수의 형태와 이름이 사용되어야 한다. 앞의 예제는 두 개의 정수형 변수 x와 y를 가지는 coord라는 이름의 구조체를 정의한다. 그러나 실제로 구조체 coord형의 변수(instances)를 생성하지는 않는다. 즉, 구조체형에 대한 메모리 영역을 보존하지 않는다. 구조체형의 변수를 실제로 생성하는 두 가지 방법이 있는데, 하나는 다음과 같이 구조체 정의문 다음에 하나 이상의 변수 이름을 포함시키는 것이다.

  struct coord {
     int x;
     int y;
  } first, second;

앞의 문장은 구조체 coord를 정의하고 coord형 변수인 first와 second라는 이름의 두 구조 체를 선언한다. first와 second는 coord형으로 선언된 구조체형 변수이다. first는 x와 y라는 이름의 두 가지 정수형 멤버를 가지고, second도 first와 같은 멤버를 가짐. 구조체를 선언하는 첫 번째 방법은 구조체의 선언문과 정의를 결합시킨 형태이다. 두 번째 방법은 소스 코드의 서로 다른 부분에서 구조체를 정의하고 구조체형 변수를 선언하는 것이다. 그래서 다음과 같은 문장도 coord형의 두 변수를 선언한다.

  struct coord {
     int x;
     int y;
     };
  /* 그 밖의 프로그램 문장 */
  struct coord first, second;


1.2 구조체 멤버를 사용하는 방법
 : 개별적인 구조체 멤버는 동일한 형태의 일반적인 변수와 마찬가지 방법으로 사용될 수 있다. 구조체 멤버는 구조체 멤버 연산자(structure member operator)나 멤버 연산자(dot operator)라고 하는 마침표(.)를 사용하여 참조할 수 있다. 그래서 first라는 이름의 구조체가 좌표 x=50, y=100의 값으로 화면 위치를 표현하도록 하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

  first.x = 50;
  first.y = 100;

구조체 second에 저장된 화면의 좌표값을 출력하기 위해서는 다음과 같은 문장을 사용할 수 있다.

  printf("%d, %d", second.x, second.y);

여기서, 개별적인 변수 대신에 구조체를 사용하는 것이 어떤 장점을 제공하는지 의문을 가질 것이다. 한 가지 중요한 장점은 간단한 할당문으로 동일한 형태의 구조체 간에 모든 값을 복사할 수 있다는 것이다. 앞의 예에서 다음 문장은

  first = second;

다음과 같은 뜻을 가진다.

  first.x = second.x;
  first.y = second.y;

프로그램에서 많은 멤버를 가지는 복잡한 구조체를 사용할 때 이렇게 한 번의 할당문으로 값을 복사할 수 있다는 사실은 매우 효율적인 시간 절약 방법이다. 구조체의 다른 한 가지 장점은 약간 더 고급 기능을 배울 때 이해할 수 있을 것이다. 일반적으로, 여러 가지 형태의 변수를 동시에 다룰 필요가 있을 때에는 항상 구조체를 유용하게 사용할 수 있을 것이다. 예를 들어, 우편용 주소록 데이터베이스를 처리할 때 각각의 자료를 구조체로 만들어 하나의 자료에 포함되는 이름, 주소, 전화번호 등을 구조체 멤버로 취급할 수 있을 것이다.

2. 더욱 복잡한 구조체
지금까지 간단한 형태의 구조체에 대해서 알아보았으므로, 이제 더욱 흥미롭고 복잡한 형태의 구조체에 대해서 알아보자. 복잡한 형태의 구조체란 다른 어떤 구조체를 멤버로 가지거나 또는 배열을 멤버로 가지는 구조체를 말한다.

2.1 구조체를 가지는 구조체
: 앞에서도 설명했듯이, C의 구조체는 C에서 사용되는 모든 데이터형을 포함할 수 있다. 예를 들어, 구조체는 다른 어떤 구조체를 가질 수 있다. 앞에서 사용했던 예를 다시 보자. 그래픽 프로그램이 좌표뿐 아니라 사각형을 다루어야 한다고 가정해 보자. 사각형(rectangle)은 축을 중심으로 상반되는 위치에 있는 좌표로 표현할 수 있다. 앞에서는 이미 하나의 점을 표현하기 위해서 두 좌표값을 가지는 구조체를 정의하는 방법에 대해서 설명했다. 사각형을 표현하는 구조체를 정의하기 위해서는 두 개의 점을 표현하는 구조체가 필요할 것이다. coord형의 구조체가 이미 정의되어 있다고 가정하면 다음과 같은 구조체를 정의할 수 있을 것이다.

  struct rectangle {
     struct coord topleft;
     struct coord bottomrt;
  };

이 문장은 두 개의 coord형 구조체 변수를 가지는 rectangle이라는 구조체를 정의한다. 두 개의 coord형 구조체 변수는 topleft와 bottomrt라는 이름을 가진다. 앞의 에는 단지 rectangle형의 구조체를 정의한다. 실제로 구조체형 변수를 선언하기 위해서는 다음과 같은 문장을 사용해야 한다.

  struct rectangle mybox;

또한, 앞에서 coord형 구조체를 선언할 때와 마찬가지로 구조체 정의와 선언을 결합시킬 수 있을 것이다.

  struct rectangle{
      struct coord topleft;
      struct coord bottomrt;
   } mybox;

여기서 int형의 데이터를 참조하기 위해서는 멤버 연산자(.)를 두 번 사용해야 할 것이다. 그래서 다음 수식은

  mybox.topleft.x

mybox라는 이름의 rectangle형 구조체 변수에서 topleft라는 멤버 내의 x라는 멤버를 나타내는 것이다. 좌표가 (0, 10), (100, 200)인 사각형을 정의하기 위해서는 다음과 같은 문장을 작성할 것이다.

  mybox.topleft.x = 0;
  mybox.topleft.y = 10;
  mybox.bottomrt.x = 100;
  mybox.bottomrt.y = 200;

이 문장은 다소 혼란스러울 것이다. rectangle형 구조체와 여기에 포함된 두 개의 coord형 구조체, 각각의 coord형 구조체가 가지는 두 개의 int형 변수의 관계를 보여주는 <그림 11.1> 을 살펴보면 지금까지 설명한 내용을 쉽게 이해할 수 있을 것이다.

구조체는 앞의 예에서와 같은 이름을 가진다.


<그림 11.1> 구조체, 구조체 내의 구조체, 구조체 멤버 간의 관계를 보여주는 그림

이제, 구조체를 포함하는 구조체의 사용 예를 살펴보도록 하자. <리스트 11.1>에 있는 프로그램은 사각형의 좌표값을 읽어들이고, 사각형의 면적을 계산하여 출력한다. 프로그램의 앞 부분에 있는 주석문에서 설명되는 프로그램의 조건을 주의해서 살펴보자.

<리스트 11.1> 다른 구조체를 가지는 구조체의 사용 예

 /* 다른 구조체를 가지는 구조체의 사용 예 */


 /* 사각형의 구석 좌표를 읽어서 면적을 구한다.

    오른쪽 하단 구석의 y좌표가 왼쪽 상단 구석의 y좌표보다 크고,

    오른쪽 하단 구석의 x좌표가 왼쪽 상단 구석의 y좌표보다 크며,

    모든 좌표가 양수라고 가정한다. */


 #include <stdio.h>


 int length, width;

 long area;


 struct coord{

     int x;

     int y;

 };


 struct rectangle{

     struct coord topleft;

     struct coord bottomrt;

 } mybox;


 main()

 {

    /* 좌표 입력 */


    printf("\nEnter the top left x coordinate: ");

    scanf("%d", &mybox.topleft.x);


    printf("\nEnter the top left y coordinate: ");

    scanf("%d", &mybox.topleft.y);


    printf("\nEnter the bottom right x coordinate: ");

    scanf("%d", &mybox.bottomrt.x);


    printf("\nEnter the bottom right y coordinate: ");

    scanf("%d", &mybox.bottomrt.y);


    /* 길이와 높이 계산 */


    width = mybox.bottomrt.x - mybox.topleft.x;

    length = mybox.bottomrt.y - mybox.topleft.y;


    /* 면적 계산과 출력 */


    area = width * length;

    printf("\nThe area is %ld units.\n", area);


    return 0;

 }

C는 구조체의 종속 단계에 제한을 두지 않는다. 메모리가 허용하는 범위 내에서는 구조체를 가지는 구조체, 이런 구조체를 가지는 또다른 구조체등 몇 단계로 구성되는 구조체를 사용할 수 있다. 그러나 여러 단계의 종속된 구조체가 효율적이지 않은 경우도 있다. 대부분의 C 프로그램에서는 3단계 이상 종속된 구조체를 사용하지 않는다.

2.2 배열을 가지는 구조체
 : 또한, 하나 이상의 배열을 멤버로 포함하는 구조체를 정의할 수 있다. 이때 사용되는 배열은 int, char등 C의 어떤 데이터형이든지 될 수 있다. 예를 들어, 다음 문장은

  struct data{
     int x[4];
     char y[10];
  };

4개의 요소를 가지는 x라는 이름의 정수형 배열과 10개의 요소를 가지는 y라는 이름의 문자형 배열을 구조체 멤버로 포함하는 data형 구조체를 정의한다. 그리고 나서 다음과 같이 data형 구조체 변수 record를 선언할 수 있다.

  struct data record;

이 구조체의 상태가 <그림 11.2>에 나타나 있다. 그림에서 배열 x의 각 요소는 배열 y의 각 요소보다 2배나 많은 공간을 차지한다는 것을 주목하기 바란다. 이것은 int형이 대개 2바이트의 저장 영역을 요구하는 반면에 char형이 1바이트를 차지하기 때문이다.


<그림 11.2> 배열을 구조체 멤버로 가지는 구조체

구조체 멤버인 배열의 개별적인 요소를 사용하기 의해서는 멤버 연산자와 배열의 첨자를 함께 사용해야 한다.

   record.x[2] = 100;
   record.y[1] = 'x';

아마도 문자 배열은 문자열을 저장하기 위해서 가장 많이 사용된다는 것을 기억할 것이다. 또한 "포인터에 대해서"에서 설명했듯이 괄호를 포함함지 않는 배열의 이름은 배열에 대한 포인터를 뜻한다는 사실을 기억할 것이다. 이런 사실은 구조체 멤버로 사용되는 배열에도 적용되므로 다음 수식은

   record.y

구조체 record의 멤버인 배열 y[]의 첫 번째 요소에 대한 포인터이다. 그래서 다음과 같은 문장을 사용하여 y[]의 내용을 화면 상에 출력할 수 있을 것이다.

   puts(record.y);

이제 다른 예제를 살펴보자. <리스트 11.2>에 있는 프로그램은 float형 변수와 두 개의 char형 배열을 가지는 구조체를 사용하고 있다.

<리스트 11.2> 배열을 멤버로 가지는 구조체의 사용 예

 /* 배열을 멤버로 가지는 구조체 */


 #include <stdio.h>


 /* 데이터를 저장할 구조체 정의와 선언 구조체는 하나의 부동 소수형 변수와

    두 개의 문자형 배열을 가진다. */


 struct data{

    float amount;

    char fname[30];

    char lname[30];

 } rec;


 main(}

 {

    /* 키보드에서 데이터 입력 */


    printf("Enter the donor's first and last names,\n");

    printf("separated by a space: ");

    scanf("%s %s", rec.fname, rec.lname);


    printf("\nEnter the donation amount: ");

    scanf("%f", &rec.amount);


    /* 정보 출력 */

    /* 참고 : %2f는 부동 소수형 값을 소수점 이하 두 자리까지 출력하도록 지정한다. */

    /* 화면상에 데이터 출력 */


    printf("\nDonor %s %s gave $%.2f.\n", rec.fname, rec.lname, rec.amount);


    return 0;

 }

3. 구조체 배열
 : 배열을 멤버로 가지는 구조체를 사용할 수 있다면 구조체의 배열을 사용하는 것도 가능할까? 가능하다고 생각할 것이다. 실제로 구조체의 배열은 아주 강력한 프로그래밍 도구다. 구조체의 배열에 대해서 알아보도록 하자.
 앞에서는 프로그램에서 필요한 데이터형을 정의하기 위해서 구조체를 사용할 수 있다고 설명했다. 프로그램은 대부분의 경우 여러 개의 데이터를 사용한다. 예를 들어, 전화번호부를 관리하는 프로그램에서는 사람의 이름과 전화번호를 저장하기 위해서 구조체를 정의할 수 있다.

   struct entry{
      char fname[10];
      char lname[12];
      char phone[8];
   };

그러나 전화번호부는 여러 사람의 데이터를 포함하는 것이므로 entry 구조체형 변수를 한 번만 선언하는 것은 유용하지 않을 것이다. 여기서 필요한 것은 entry형 구조체의 배열이다. 그래서 구조체를 정의하고 나면 다음과 같이 구조체의 배열을 선언할 수 있다.

   struct entry list[1000];

이 문장은 1,000개의 요소를 가지는 list라는 이름의 배열을 선언한다. 모든 요소는 entry형 구조체 변수이고, 각 요소는 일반적인 배열 요소와 마찬가지로 첨자에 의해서 구분된다. 배열 요소인 각각의 구조체는 세 개의 char형 배열을 구조체 멤버로 포함하고 있다. 구조체의 배열을 선언할 때에는 다양한 방법으로 데이터를 다룰 수 있다. 예를 들어, 한 배열 요소의 데이터를 다른 배열 요소에 할당하기 위해서 다음과 같이 할 수 있을 것이다.

   list[1] = list[5];

이 문장은 list[5]의 각 멤버에 저장된 값을 구조체 list[1]에서 대응하는 멤버에 할당한다. 또한, 개별적인 구조체의 멤버들 간에 데이터를 이동시킬 수도 있다. 다음 문장은

   strcpy(list[1].phone, list[5].phone);

list[5].phone에 저장된 문자열을 list[1].phone에 복사하는 것이다. 라이브러리 함수 strcpy()는 한 문자열을 다른 문자열에 복사한다. 자세한 내용은 "문자열 다루기"에서 상세히 설명하겠다. 또한, 필요하다면 구조체 멤버의 하나인 배열의 개별적인 요소들 간에도 데이터를 이동시킬 수 있다.

   list[5].phone[1] = list[2].phone[3];

이 문장은 list[5]에서 전화번호 값을 저장하는 배열의 두 번째 위치에 list[2]에서 전화번호값을 저장하는 배열의 네 번째 문자를 할당한다. 첨자는 항상 0에서부터 시작한다는 것을 잊지 않도록 하자.

<리스트 11.3>에 있는 프로그램은 구조체 배열의 사용 예를 보여준다. 또한, 이 프로그램은 배열을 멤버로 가지는 구조체의 배열을 사용하고 있다.

    <리스트 11.3> 

 /* 구조체의 배열 사용 예 */


 #include <stdio.h>


 /* 항목을 저장할 구조체 정의 */


 struct entry{

    char fname[20];

    char lname[20];

    char phone[10];

 };


 /* 구조체의 배열 선언 */


 struct entry list[4];


 int i;


 main()

 {

    /* 네 명의 데이터 입력 */


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

    {

       printf:\nEnter first name: ");

       scanf("%s", list[i].fname);

       printf("Enter last name: ")"

       scanf("%s", list[i].lname);

       printf("Enter phone is 123-4567 format: ");

       scanf("%s", list[i].phone);

    }

    /* 두 개의 빈줄 출력 */

    printf("\n\n");


    /* 데이터 출력 */


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

    {

       printf("Name: %s %s", list[i].fname, list[i].lname);

       printf("\t\tPhone: %s\n", list[i].phone);

    }

    return 0;

 }

<리스트 11.3>에서 사용된 프로그래밍 방식에 익숙해지도록 하자. 많은 프로그래밍 작업에서는 배열을 멤버로 가지는 구조체의 배열을 사용할 필요가 있을 것이다.

4. 구조체의 초기화
 : C의 다른 변수형과 마찬가지로 구조체를 선언하는 동시에 초기화할 수 있다. 수행 과정은 배열을 초기화하는 것과 비슷하다. 구조체 선언문에는 등호와 함께 쉼표에 의해서 구분되고, 중괄호에 포함되는 초기화값의 목록이 나타난다. 예를 들어, 다음 예제를 살펴보자.

   struct sale {
      char customer[20];
      char item[20];
      float amount;
   } mysale = {"Acme Industries",
               "Left-handed widget",
                1000.00
              };

 이 문장이 실행되면 다음과 같은 동작이 수행된다.

① 1번째 줄부터 5번째 줄까지는 sale이라는 이름의 구조체형을 정의한다.
② 5번째 줄에서는 mysale이라는 이름의 sale형 구조체 변수가 선언된다.
③ 5번째 줄에서는 구조체 멤버 mysale.customer를 문자열 'Acme Industries'로 초기화한다.
④ 6번째 줄에서는 구조체 멤버 mysale.item을 문자열 'Left-handed widget'으로 초기화한다.
⑤ 7번째 줄에서는 구조체 멤버 mysale.amount를 1000.00의 값으로 초기화한다.

구조체를 멤버로 가지는 구조체의 경우에는 순서대로 초기화값을 나열하면 된다. 초기화값은 구조체를 정의하는 문장에서 멤버가 나타나 있는 순서대로 구조체 멤버에 할당된다. 다음은 앞의 예제를 약간 보충한 다른 하나의 예제이다.

   struct customer {
      char firm[20];
      char contact[25];
   }

   struct sale {
      struct customer buyer;
      char item[20];
      float amount;
   } mysale = {{"Acme Industries", "George Adams"),
                "Left-handed widget",
                 1000.00
               };

이 예제는 다음과 같은 초기화를 수행한다.

① 10번째 줄에서 구조체 멤버 mysale.buyer.firm은 문자열 'Acme Industries'로 초기화된다.
② 10번째 줄에서 구조체 멤버 mysale.buyer.contact는 문자열 'George Adams'로 초기화된다.
③ 11번째 줄에서 구조체 멤버 mysale.item은 문자열 'Left-handed widget'으로 초기화된다.
④ 12번째 줄에서 구조체 멤버 mysale.amount는 1000.00의 값으로 초기화된다.

또한, 구조체의 배열을 초기화할 수 있다. 입력되는 초기화값은 배열에 포함되어 있는 구조체에 순서대로 할당된다. 예를 들어, sale형 구조체의 배열을 선언하고 나서 처음 두 배열 요소인 두 개의 구조체를 초기화하기 위해서는 다음 예제와 같이 할 수 있을 것이다.

  struct customer {
     char firm[20];
     char contact[25];
     };

  struct sale {
     struct customer buyer;
     char item[20];
     float amount;
     };

  struct sale y1990[100] = {
     {{"Acme Industries", "George Adams"},
       "Left-handed widget",
       1000.00
      }

     {{"Wilson & Co.", "Ed Wilson"},
       "Type 12 gizmo",
        290.00
      }
     };

이 코드에서는 다음과 같은 동작이 수행된다.

① 14번째 줄에서 구조체 멤버 y1990[0].buyer.firm은 문자열 'Acme Industries'로 초기화된다.
② 14번째 줄에서 구조체 멤버 y1990[0].buyer.contact는 문자열 'George Adams'로 초기화됨.
③ 15번째 줄에서 구조체 멤버 y1990[0].item은 문자열 'Left-handed widget'으로 초기화된다.
④ 16번째 줄에서 구조체 멤버 y1990[0].amount는 1000.00의 값으로 초기화된다.
⑤ 18번째 줄에서 구조체 멤버 y1990[1].buyer.firm은 문자열 'wilson & Co.'로 초기화된다.
⑥ 18번째 줄에서 구조체 멤버 y1990[1].buyer.contact는 문자열 'Ed Wilson'으로 초기화된다.
⑦ 19번째 줄에서 구조체 멤버 y1990[1].item은 문자열 'Type 12 gizmo'로 초기화된다.
⑧ 20번째 줄에서 구조체 멤버 y1990[1].amount는 290.00의 값으로 초기화된다.

5. 구조체와 포인터
 : 포인터가 C에서 아주 중요한 부분을 차지한다는 사실을 인식한다면 구조체와 함께 사용할 수 있다는 사실도 쉽게 짐작할 수 있을 것이다. 포인터를 구조체의 멤버로 사용할 수 있으며, 구조체에 대한 포인터를 선언할 수도 있다. 이런 내용들을 하나씩 살펴보도록 하자.

5.1 구조체 멤버로 사용되는 포인터
 : 포인터를 구조체 멤버로 사용하면 더욱 융통성 있는 프로그램을 작성할 수 있다. 포인터는 구조체 멤버로 사용될 때 일반적인 경우와 동일한 방법으로 선언된다. 즉, 간접 연산자(*)를 사용하여 선언된다. 다음은 예이다.

   struct data {
      int *value;
      int *rate;
      } first;

이 문장은 int형에 대한 두 개의 포인터를 멤버로 가지는 구조체를 정의하고 선언한다. 다른 모든 포인터에서와 마찬가지로 포인터를 선언하는 것만으로 사용할 수는 없다. 포인터에는 변수의 주소를 할당하여 포인터가 어떤 영역을 지적하도록 초기화해야 한다. 만약 cost와 interest가 int형 변수로 선언되어 있다면 다음과 같은 문장을 작성할 수 있을 것이다.

   first.value = &cost;
   first.rate = &interest;

이제 포인터가 초기화되었으므로 간접 연산자(*)를 사용할 수 있다. 수식 *first.value는 cost에 저장된 값을 뜻하고, 수식 *first.rate는 interest에 저장된 값을 뜻한다. 구조체 멤버로 가장 많이 사용되는 포인터는 아마도 char형에 대한 포인터일 것이다. 앞 강의 "문자와 문자열"에서는 '첫 번째 문자를 지적하는 포인터와 마지막을 표시하는 널 문자 사이의 일련의 문자로 문자열이 구성된다'는 것을 설명했었다. 메모리를 효율적으로 사용하기 위해서는 다음과 같이 char형에 대한 포인터를 선언하고 문자열을 지적하도록 초기화해야 한다.

   char *p_message;
   p_message = "Teach Yourself C in 21Days";

또한, char형에 대한 포인터를 멤버로 가지는 구조체를 사용하여 동일한 결과를 얻을 수 있다.

   struct msg {
      char *p1;
      char *p2;
      } myptrs;
   myptrs.p1 = "Teach yourself C in 21Days";
   myptrs.p2 = "By SAMS Publishing";

구조체 멤버로 포함되어 있는 포인터는 일반적인 포인터를 사용할 수 잇는 어떤 곳에서든지 사용할 수 있다. 예를 들어, 포인터가 지적하는 문자열을 출력하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

   printf("%s %s", myptrs.p1, myptrs.p2);

구조체 멤버로 char형의 배열을 사용하는 것과 char형에 대한 포인터를 사용하는 것의 차이점은 무엇일가? 두 가지 모두 구조체에 문자열을 '저장'하기 위한 방법이다. 다음은 두 가지 방법을 모두 사용하는 구조체 msg이다.

   struct msg(
       char p1[30];
       char *p2;
       } myptrs;

괄호를 포함하지 않는 배열의 이름은 배열의 처 번째 요소에 대한 포인터라는 것을 기억하자 그래서 앞의 구조체 멤버는 다음과 같이 사용할 수 있다.

   strcpy(myptrs.p1, "Teach Yourself C In 21 Days");
   strcpy(myptrs.p2, "By SAMS Publishing");
   /* 그 밖의 프로그램 문장 */
   puts(myptrs.p1);
   puts(myptrs.p2);

두 가지 방법의 실제 차이점은 무엇일까? 만약 구조체 멤버로 char형 배열을 가지는 구조체를 정의하면, 이 구조체형으로 선언되는 모든 변수는 일정한 크기의 배열만을 저장할 수 있는 저장 영역을 가지게 된다. 또한, 저장 영역의 크기는 제한되고 구조체에는 지정된 것보다 긴 문자열을 저장할 수 없다. 다음은 사용 예이다.

   struct msg {
      char p1[10];
      char p2[10];
      } myptrs;
      …
   strcpy(p1, "Minneapolis");  /* 문자열이 배열보다 길다. */
   strcpy(p2, "MN");          /* 문자열이 배열보다 잛지만 저장 영역이 낭비되고 있다. */

반면에, char형에 대한 포인터를 가지는 구조체를 정의하면 이런 제한은 적용되지 않는다. 각각의 구조체 변수는 포인터를 저장하기 위한 저장 영역만을 가지게 된다. 실제 문자열은 메모리 내의 다른 어떤 곳에 저장되지만 메모리의 위치에 대해서는 신경 쓸 필요가 없다. 이렇게 포인터를 사용하면 문자열의 길이에 제한이 없고 저장 영역이 낭비되지 않는다. 실제 문자열은 구조체의 일부분이 아닌 상태로 메모리의 특정 부분에 저장된다. 구조체에 포함되는 각 포인터는 어떤 길이의 문자열도 지적할 수 있다. 실제 문자열이 구조체에 저장되는 것은 아니지만 구조체의 일부분과 관련된다.

5.2 구조체에 대한 포인터
: C 프로그램에서는 다른 어떤 데이터형에 대한 포인터와 마찬가지로 구조체에 대한 포인터 를 선언하고 사용할 수 있다. 이 장에서 나중에 설명할 것처럼 구조체에 대한 포인터는 함수 의 인수로 구조체를 전달할 때 가끔 사용된다. 또한, 구조체에 대한 포인터는 링크드 리스트 (linked lists)라고 알려져 있는 매우 강력한 데이터 저장 방법에서 사용된다. 링크드 리스트는 나중에 "포인터 : 고급 기능들"에서 설명할 것이다.
 여기서는 프로그램에서 구조체에 대한 포인터를 생성하고 사용하는 방법에 대해서 알아보도록 하자. 우선, 구조체를 정의하겠다.

   struct part {
      int number;
      char name[10];
      };

이제 part형 구조체에 대한 포인터를 선언한다.

   struct part *p_part;

선언문에서 사용되는 간접 연산자(*)는 p_part가 part형 구조체 변수가 아니라 part형 구조체 에 대한 포인터라는 사실을 알려준다는 것을 기억하기 바란다. 이제 포인터를 초기화할 수 있을까? 그렇지 않다. 구조체 part는 정의되었지만 어떤 구조체 변수도 선언되지 않았다. 앞의 문장은 구조체 변수 선언문이 아니라 구조체에 대한 포인터 선언문이라는 것을 기억하자. 이제 데이터를 저장하기 위해서 메모리 내의 저장 영역을 보존 하는 변수 선언문이 필요하다. 포인터가 어떤 주소를 지적하도록 하기 위해서는 우선 part형 구조체 변수를 선언해야 한다. 다음은 선언문이다.

   struct part gizmo;

이제 포인터를 초기화할 수 있다.

   p_part = &gizmo;

이 문장은 gizmo의 주소값을 p_part에 저장한다.

<그림 11.5>에는 구조체와 구조체에 대한 포인터의 관계가 나타나 있다.


<그림 11.5> 구조체에 대한 포인터는 구조체의 첫 번째 바이트를 지적한다.

이제, 구조체 gizmo에 대한 포인터를 생성했다. 이 포인터를 어떻게 사용할 것인가? 한 가지 방법은 간접 연산자(*)를 이용하는 것이다. 이런 사실을 여기에 적용하면 p_part는 구조체 gizmo에 대한 포인터이고, *p_part는 gizmo 자체를 뜻한다는 것을 알 수 있다. 이제 gizmo의 각 멤버를 사용하기 위해서는 구조체 멤버 연산자(.)를 사용하자. gizmo.number에 100의 값을 저장하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

   (*p_part).number = 100;

여기서 멤버 연산자(.)는 간접 연산자(*)보다 높은 우선 순위를 가지고 있으므로 *p_part는 괄호 내에 포함되어야 한다. 구조체에 대한 포인터를 사용하여 구조체 멤버를 참조하는 두 번째 방법은 하이픈(-)과 부등호(>)로 구성되는 간접 멤버 참조 연산자(indirection membership operator)를 사용하는 것이다. 하이픈과 부등호를 함께 사용하면 C에서 하나의 새로운 연산자로 간주된다는 것을 기억하기 바란다. 이 연산자는 포인터 이름과 멤버 이름 사이에 위치된다. p_part 포인터를 사용하여 gizmo의 멤버인 number를 참조하기 위해서는 다음과 같은 문장을 사용할 수 있다.

   p_part -> number

다른 하나의 예를 살펴보자. 만약 str이 구조체이고, p_str이 str에 대한 포인터이며, memb가 str의 멤버라면 다음과 같은 문장을 통해서 str.memb를 사용할 수 있을 것이다.

   p_str->memb

그래서 전체적으로 구조체 멤버를 참조하는 세 가지 방법이 있음을 알 수 있다.

·구조체의 이름을 사용한다.
·간접 연산자(*)와 구조체에 대한 포인터를 함께 사용한다.
·간접 멤버 참조 연산자(->)와 구조체에 대한 포인터를 함께 사용한다.

p_str이 구조체 str에 대한 포인터라면 다음의 세 가지 수식은 모두 동일한 것이다.

   str.memb
   (*p-str).memb
   p_str->memb

5.3 포인터와 구조체의 배열
: 앞에서는 구조체의 배열과 구조체에 대한 포인터가 매우 강력한 프로그래밍 도구라는 것을 설명했다. 또한, 구조체를 참조하는 포인터를 배열 요소로 사용해서 두 가지를 결합시킬 수도 있다. 앞에서 예제로 사용했던 구조체를 다시 한 번 살펴보도록 하자.

   struct part{
      int number;
      char name[10];
      };

구조체 part가 정의되면 part형 배열을 선언할 수 있다.

   struct part data[100];

다음으로 part형에 대한 포인터를 선언하고, 포인터가 배열 data의 첫 번째 구조체를 지적하도록 초기화할 수 있다.

   struct part *p_part;
   p_part = &data[0];

괄호를 포함하지 않는 배열의 이름이 배열의 첫 번째 요소에 대한 포인터라는 것을 기억하자 그래서 두 번째 문장은 다음과 같이 작성할 수도 있을 것이다.

   p_part = data;

이제 part형 구조체의 배열과 배열의 첫 번째 요소, 즉 배열의 첫 번째 구조체에 대한 포인터를 생성했다. 다음과 같은 문장을 사용하여 첫 번째 요소의 내용을 출력할 수 있을 것이다.

   printf("%d %s", p_part->number, p_part->name);

만약 배열의 모든 요소를 출력하기 원한다면 어떻게 해야 할까? 아마도 순환문이 한 번 반복될 때마다 배열의 요소를 하나씩 출력하는 for문을 사용할 것이다. 포인터를 사용하여 구조체 멤버를 참조하기 위해서는 순환문이 반복될 때마다 포인터 p_part가 배열의 다음 요소, 즉 배열의 다음 구조체를 지적하도록 변경해 주어야 한다. 어떻게 해야 여기서 의도 하는 대로할 수 있을까? 여기서는 C의 포인터 연산을 사용할 필요가 있다. 단항 증가 연산자(++)는 포인터와 함께 사용될 때 특별한 의미를 가지게 된다. 즉, '포인터가 지적하는 데이터형의 크기를 반영하여 포인터의 값을 증가시켜라'는 것을 뜻한다. 예를 들어, obj형의 변수를 지적하는 포인터 ptr이 있다면 다음 문장은

   ptr++;

다음과 같은 뜻을 가진다.

   ptr += sizeof(obj);

포인터 연산의 이런 특징은 배열에서 특히 유용하게 사용된다. 배열의 각 요소는 메모리에 순서대로 저장된다. 포인터가 배열의 요소를 지적하고 있을 때 증가(++) 연산자를 사용하면 포인터는 n + 1번째의 요소를 지적하게 된다. <그림 11.6>에서는 4바이트 크기의 요소로 구성되는 x[]라는 이름의 배열을 예로 들어 이런 사실을 설명한다. 그림에서는 2바이트 길이의 두 int형 변수를 멤버로 가지는 구조체가 사용되고 있다. 포인터 ptr은 x[0]을 지적하도록 초기화되어 있고 ptr의 값이 증가될 때마다 배열의 다음 요소를 지적한다.


<그림 11.6> 포인터의 값이 증가될 때 포인터는 배열의 다음 요소를 '지적'한다.

결국, 프로그램에서 포인터를 증가시켜 구조체의 배열이나 다른 어떤 데이터형의 배열을 순서대로 사용할 수 있다는 사실을 알 수 있다. 이 방법은 같은 작업을 수행하기 위해서 배열의 첨자를 사용하는 것보다 쉽고 간단하다. <리스트 11.4>에 있는 프로그램은 지금까지 설명한 내용을 예제로 보여준다.

<리스트 11.4> 포인터를 증가시켜 배열의 요소를 순서대로 사용하는 프로그램

 /* 포인터 표기를 사용하여 구조체의 배열을 차례대로 사용하는 예 */


 #include <stdio.h>


 #define MAX 4


 /* 구조체를 정의 후 4구조체의 배열을 선언하고 초기화한다. */


 struct part {

    int number;

    char name[10];

 } data[MAX] = {1, "Smith", 2, "Jones", 3, "Adams", 4, "Wilson"};


 /* part형에 대한 포인터와 카운터 변수 선언 */


 struct part *p_part;

 int count;


 main()

 {

    /* 첫 배열 요소에 대한 포인터 초기화 */


    p_part = data;


    /* 순환할 때마다 포인터를 증가시키며 배열을 사용한다. */


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

    {

       printf("\nAt address %d: %d %s", p_part, p_part->number, p_part->name);

       p_part++;

    }


    return 0;

 }

출력되는 주소값을 자세히 살펴보자. 실제 값은 시스템마다 달라질 수 있지만 간격은 일정하게 증가할 것이다. 이 간격은 구조체 part의 크기로 대부분의 시스템에서는 12가 될 것이다. 이 프로그램은 포인터를 증가시킬 때 포인터가 지적하는 변수의 크기를 반영하여 주소값이 증가된다는 사실을 분명히 보여준다.

5.4 함수의 인수로 구조체를 전달하는
 : 다른 데이터형과 마찬가지로 함수의 인수로 구조체를 전달할 수도 있다. <리스트 11.5>에 있는 프로그램은 이런 사실을 증명한다. 이 프로그램은 <리스트 11.2>에 있는 프로그램을 수정한 것이다. 여기에서는 화면 상에 데이터를 출력하는 함수를 사용하는 반면에, <리스트 11.2>는 main()의 일부분에 해당하는 문장에서 데이터를 출력한다.

<리스트 11.5> 함수의 인수로 구조체를 전달하는 프로그램

 /* 함수에 구조체를 전달하는 예 */


 #include <stdio.h>


 /* 데이터를 저장할 구조체 선언과 정의 */


 struct data {

    float amount;

    char fname[30];

    char lname[30];

 } rec;


 /* 함수 원형. 함수는 복귀값이 없고 하나의 인수로 data형의 구조체를 받아들인다. */


 void print_rec(struct data x);


 main()

 {

    /* 키보드에서 데이터 입력 */


    printf("Enter the donor's first and last name,\n");

    printf("separated by a space: ");

    scanf("%s %s", rec.fname, rec.lname);


    printf("\nEnter the donation amount: ");

    scanf("%f", &rec.amount);


    /* 출력 함수 호출 */

    print_rec( rec );


    return 0;

 }


 void print_rec(struct data x)

 {

    printf("\nDonor %s %s gave $%.2f.\n", x.fname, x.lname, x.amount);

 }

구조체의 주소, 즉 구조체에 대한 포인터를 인수로 사용하여 함수에 구조체를 전달할 수도 있다. 이전에는 C에서 구조체를 인수로 사용하는 것이 유일한 방법이었다. 이제는 이렇게 구조체를 직접 전달하지 않지만, 아직까지도 가끔 구조체를 전달하는 경우를 볼 수 있다. 구조체에 대한 포인터를 인수로 전달하면 함수에서는 구조체 멤버를 참조하기 위해서 간접 멤버 참조 연산자(->)를 사용해야 한다는 것을 기억하기 바란다.

6. 공용체
: 공용체(unions)는 구조체와 비슷하다. 공용체는 구조체와 같은 방법으로 선언되고 사용된다. 공용체는 한 번에 하나의 멤버만이 사용될 수 있다는 점에서 구조체와 다르다. 그 이유는 간단하다. 공용체의 모든 멤버는 메모리에서 같은 영역을 차지하고 있다. 모든 멤버는 겹쳐져 있는 셈이다.

6.1 공용체 정의와 선언, 그리고 초기화
: 공용체는 구조체와 같은 방법으로 정의되고 선언된다. 선언문에서 유일한 차이점은 키워드 struct 대신에 union이 사용된다는 점이다. char 변수와 정수형 변수의 간단한 공용체를 정의하기 위해 다음과 같이 작성할 수 있다.

   union shared {
      char c;
      int i;
      };

이 공용체 shared는 문자값 c나 정수값 i의 하나를 가질 수 있는 공용체 변수(instance)를 생성하는 데 사용될 수 있다. 이것은 OR 조건문과도 같다. 두 값을 모두 가지게 되는 구조체와 달리 공용체는 한 번에 하나의 값만을 가질 수 있다.

<그림 11.7>은 shared 공용체가 메모리에 저장되는 방법을 보여준다.


<그림 11.7> 공용체는 한 번에 하나의 값을 가질 수 있다.

공용체를 선언하는 동시에 초기화할 수도 있다. 그러나 한 번에 하나의 멤버만이 사용될 수 있으므로 하나만 초기화될 수 있다. 일반적으로, 혼란을 막기 위해서 공용체의 첫 멤버만이 초기화될 수 있다 .다음 코드는 shared 공용체형 변수가 선언되고 초기화되는 것을 보여준다.

   union shared generic_variable = ('@'};

6.2 공용체 멤버 사용하기
 : 개별적인 공용체 멤버는 구조체 멤버와 마찬가지로 멤버 연산자인 마침표(.)를 이용해서 사용할 수 있다 .그러나 공용체 멤버를 사용할 때에는 중요한 한 가지 차이점이 있다. 한 번에 하나의 공용체 멤버만이 사용되어야 한다는 것이다. 공용체는 서로 겹쳐져 있으므로 한 번에 하나의 멤버를 사용하는 것은 매운 중요하다. <리스트 11.6>은 예제를 보여준다.

<리스트 11.6> 공용체의 잘못된 사용 예

 /* 한번에 하나 이상의 공용체 멤버 사용 */

 #include <stdio.h>


 main()

 {

    union shared_tag {

       char c;

       int i;

       long l;

       float f;

       double d;

    } shared;


    shared.c = '$';


    printf("\nchar c = %c", shared.c);

    printf("\nint i = %d", shared.i);

    printf("\nlong l = %ld", shared.l);

    printf("\nfloat f = %f", shared.f);

    printf("\ndouble d = %f", shared.d);


    shared.d = 123456789.8765;


    printf("\nchar c = %c", shared.c);

    printf("\nint i = %d", shared.i);

    printf("\nlong l = %ld", shared.l);

    printf("\nfloat f = %f", shared.f);

    printf("\ndouble d = %f", shared.d);


    return 0;

 }

7. typedef와 구조체
 : 구조체를 정의할 때에는 동의어인 typedef 키워드를 사용할 수 있다. 예를 들어, 다음 문장은 구조체에 대한 동의어로 coord를 사용할 수 있도록 정의해준다.

   typedef struct {
      int x;
      int y;
      } coord;

이제 coord를 사용하여 구조체형의 변수를 선언할 수 있다.

   coord topleft, bottomright;

typedef는 이 장의 앞부분에서 설명한 구조체 태그와는 다르다는 것을 주의하자. 만약 다음과 같은 구조체를 정의하면

   struct coord {
       int x;
       int y;
       };

coord는 구조체의 태그로 사용된 것이다. 구조체 변수를 선언하기 위해서 이런 태그를 사용할 수 있지만 typedef와 달리 반드시 struct 키워드를 포함시켜야 한다

   struct coord topleft, bottomright;

구조체를 선언할 때 typedef를 사용해야 하는지 구조체 태그를 사용해야 하는지의 여부는 결정하기 어려운 문제이다. typedef를 사용하는 경우에는 struct 키워드가 필요하지 않으므로 더 간결하다고 볼 수 있다. 반면에, 태그를 사용하고 struct 키워드를 포함시키면 구조체라는 사실을 분명히 알 수 있어 편리하다.

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

9장 포인터에 대해서  (0) 2019.06.02
10장 문자와 문자열  (0) 2019.06.02
12장 변수의 범위  (0) 2019.06.02
13 장 고급 프로그램 제어문  (0) 2019.06.02
14장 화면, 프린터, 키보드 사용하기  (0) 2019.06.02

함수 내에서 선언된 변수가 함수의 밖에서 선언된 변수와 분명히 구분된다는 것을 설명했다. 이런 사실은 C 프로그래밍에서 중요한 변수의 범위(variable scope)를 부분적으로 설명하는 예이다.

·변수의 범위
·외부 변수의 의미와 외부 변수를 사용하지 않아야 하는 이유
·지역 변수에 대해서
·정적 변수와 자동 변수의 차이
·지역 변수와 블록에 대해서
·적절한 변수의 형태를 선택하는 방법

1. 변수의 범위란?
 : 변수의 범위(scope)는 프로그램에서 변수를 사용할 수 있는 범위나 또는 변수가 프로그램 내에서 효과를 나타낼 수 있는(visible) 범위를 말한다. C에서 변수에 대해서 설명할 때에는 변수의 값을 사용할 수 있는 가능성(accessibility)과 주어진 범위 내에서 효과를 나타내는 것을 뜻하는 유효성(visibility)을 함께 사용한다. 변수의 범위를 언급할 대 변수(variable)는 C의 모든 데이터형을 뜻한다. 즉, 간단한 변수에서부터 배열, 구조체, 포인터 등을 모두 포함한다. 또한, const 키워드로 정의된 기호 상수도 포함한다. 변수의 범위는 변수의 생명, 즉 메모리에서 변수의 값이 보존되는 기간이나 변수에 저장 영역이 할당되고 해제되는 시기에 영향을 준다. 우선, 변수의 유효성(visibility)에 대해서 알아보도록 하자.

1.1 변수의 범위를 설명하는 예제
 : <리스트 12.1>에 있는 프로그램을 살펴보자. 프로그램은 5번째 줄에서 변수 x를 정의하고, 11번째 줄에서 x의 값을 출력하기 위해서 printf() 함수를 호출하며 다시 x의 값을 출력하기 위해 printf_value()를 호출한다. 함수 print_value()에는 x의 값이 인수로 전달되지 않는다는 것에 주의하기 바란다. 이 함수는 19번째 줄에서 printf() 함수에 대한 인수로 x를 사용하고 있다.

<리스트 12.1> 변수 x는 함수 print_value내에서도 사용될 수 있다.

 /* 변수의 범위를 설명하는 에제 */


 #include <stdio.h>


 int x = 999;


 void print_value(void);


 main()

 {

    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    printf("%d\n", x);

 }

-> 출 력
  999
  999

이 프로그램은 아무런 문제 없이 컴파일되고 실행된다. 이제 이 프로그램에서 x를 정의하는 문장을 main() 함수 내의 위치로 이동시켜 보자. 변경된 소스 코드는 <리스트 12.2>에 나타나 있다.

<리스트 12.2> 변수 x는 함수 print_value 내에서 사용될 수 없다.

 /* 변수의 범위를 설명하는 예제 */


 #include <stdio.h>


 void print_value(void);


 main()

 {

    int x = 999;


    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    printf("%d\n", x);

 }

<리스트 12.2>를 컴파일하면 컴파일러는 다음과 비슷한 에러 메시지를 출력할 것이다.

   list1202.c[19] : Error: undefined identifier 'x'.

에러 메시지에서 괄호 내에 나타나는 숫자는 에러가 발생한 프로그램의 문장 번호를 뜻한다. 19번째 줄은 print_value() 함수 내에서 printf() 함수를 호출하는 문장이다. 에러 메시지는 print_value() 함수 내에서 변수 x가 '정의되지 않았다.'는 것을 알려준다. 즉, 함수 내에서는 변수 x가 유효하지 않다. 그러나 11번째 줄에 있는 printf()함수에서는 에러 메시지가 발생하지 않는다는 것에 주목할 필요가 있다. 프로그램의 11번째 줄에서는 변수 x가 유효한 것이다.

<리스트 12.1>과 <리스트 12.2>의 유일한 차이는 변수 x가 정의된 위치이다. x를 정의하는 문장을 이동시키면 변수의 범위가 바뀐다. <리스트 12.1>에서 x는 외부(external) 변수로 선언되었고, 이때 변수의 유효 범위는 전체 프로그램이다. 변수 x는 main() 함수와 print_value() 함수에서 모두 사용할 수 있다. 그러나 <리스트 12.2>에서 x는 지역(local) 변수로 선언되었고, 이때 변수의 유효 범위는 main() 함수로 제한된다. 그래서 함수 print_value()에서는 x가 존재하지 않는 것으로 간주된다 지역 변수와 전역 변수에 대해서는 나중에 상세히 설명할 것이므로 여기서는 변수 범위의 중요성을 알아둘 필요가 있다.

1.2 변수의 범위가 중요한 이유
 : 변수의 범위가 중요하다는 것을 이해하기 위해서는 구조화 프로그래밍에 대해서 다시 한 번 언급할 필요가 있다. 구조화 프로그래밍에서는 특정 작업을 수행하는 독립된 함수로 프로그램을 분할한다. 여기서 중요한 것은 독립적(independent)이라는 단어이다. 완전히 독립된 함수를 작성하기 위해서는 함수 내의 변수가 다른 함수에 의해서 간섭되지 않아야 한다. 함수 내에서 독립된 변수를 사용하면 프로그램의 다른 부분에 영향을 받지 않고 주어진 작업을 정상적인 상태로 수행하는 함수를 작성할 수 있다. 여기서, 함수들 간에 완전히 독립된 데이터를 사용하는 것이 항상 바람직하다는 것을 짐작할 수 있을 것이다. 사용되는 변수의 범위를 지정하면 함수들간에 독립된 데이터를 사용할 수 있다. 변수의 범위를 지정하는 것은 이처럼 함수의 독립성에 영향을 준다.

2. 외부 변수
 : 외부(external) 변수는 어떤 함수의 바깥에서 정의되는 것이다. main() 함수도 하나의 함수이므로 외부 변수는 main()의 박에서 선언되는 것을 포함한다. 외부 변수는 가금 전역(global) 변수라고도 한다.

2.1 외부 변수의 범위
 : 외부 변수의 범위는 전체 프로그램이다. 그래서 외부 변수는 main()이나 프로그램 내의 다른 모든 함수에서 사용될 수 있다. 예를 들어, <리스트 12.1>에 있는 변수 x는 외부 변수다. 프로그램을 컴파일하고 실행하여 살펴보았듯이 x는 두 개의 함수 main()과 print_value()에서 유효하다. 그러나 정확히 말해서 외부 변수의 유효 범위가 전체 프로그램이라는 사실은 잘못된 것이다. 외부 변수의 범위가 변수를 정의하는 전체 소스 코드 파일이라는 표현이 더 정학하다. 전체 프로그램이 하나의 소스 코드 파일로 구성된다면 두 가지 범위는 같다. 대부분의 간단한 C 프로그램은 하나의 파일로 구성되고 프로그램도 이렇게 하나의 파일로 구성된다.

2.2 외부 변수를 사용하는 시기
 : 몇 가지 예제 프로그램에서 외부 변수를 사용하고 있지만 실제로는 외부 변수를 사용하지 않도록 해야 한다. 그 이유는 무엇일까? 외부 변수를 사용하면 구조화 프로그래밍에서 중심이 되는 모듈의 독립성(modular independence)이 사라지게 된다. 모듈의 독립성은 프로그램을 구성하는 각각의 함수나 모듈이 주어진 작업을 수행하기 위해서 필요로 하는 모든 코드와 데이터를 내부에 포함하고 있는 것을 뜻한다. 지금까지 사용된 간단한 프로그램에서는 이런 모듈의 독립성이 중요하지 않게 생각될 수도 있지만, 더 크고 복잡한 프로그램을 작성하게 되면 외부 변수에 대한 함수의 의존성은 중요한 문제가 될 수 있다. 그렇다면 외부 변수는 어떤 경우에 사용해야 할까? 프로그램을 구성하는 대부분의 함수나 또는 모든 함수가 사용해야 하는 변수를 선언하는 경우에만 외부 변수를 사용하는 것이 좋다 const 키워드를 사용하여 정의되는 기호 상수는 외부 변수로 선언하기에 적합한 것이다. 변수가 몇 개의 함수에서만 사용된다면 외부 변수로 선언하지 말고 함수에 인수로 전달하자.

2.3 extern 키워드
 : 함수에서 외부 변수를 사용할 필요가 있을 때에는 extern 키워드를 사용하여 함수 내에서 변수를 선언하는 것이 좋다. 변수는 다음과 같은 형식으로 선언된다.

  extern type name;

type은 변수의 형태이고, name은 변수의 이름이다. 예를 들어, <리스트 12.1>에 포함디어 있는 함수 main()과 print_value()에는 변수 x를 선언하는 문장을 포함시킬 수 있다. <리스트 12.3>에는 수정된 프로그램이 나타나 있다.

<리스트 12.3> 변수 x는 함수 main()과 print_value에서 외부 변수로 선언된다.

 /* 외부 변수 선언 */


 #include <stdio.h>


 int x = 999;


 void print_value(void);


 main()

 {

    extern int x;


    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    extern int x;

    printf("%d\n", x);

 }

3. 지역 변수
 : 지역 변수(local variable)는 함수 내에서 정의되는 변수이다. 지역 변수의 범위는 변수가 정의된 함수로 제한된다. 다섯번째 강의 "함수의 기본"에서 함수 내에서 사용되는 지역 변수, 지역 변수를 정의하는 방법, 지역 변수의 장점에 대해서 설명했다. 지역 변수는 컴파일러에 의해서 자동으로 0의 값으로 초기화되지 않는다. 지역 변수를 정의할 때 변수를 초기화하지 않으면 지역 변수에는 임의의 값(garbage)이 저장된다. 그래서 지역 변수를 사용하기 전에는 반드시 필요한 값을 저장해야 한다. 또한, main() 함수 내에서도 지역 변수를 사용할 수 있다. <리스트 12.2>에 있는 변수 x는 main() 함수에서 사용되는 지역 변수의 예이다. 변수 x는 main() 내에서 정의되고 프로그램의 결과로도 알 수 있듯이 main() 내에서만 유효하다.

3.1 정적 변수와 자동 변수
: 지역 변수는 기본적으로 자동 변수이다. 자동 변수는 함수가 호출될 때마다 새롭게 생성되고 함수의 실행이 끝나면 사라지는 변수를 뜻한다. 좀더 정확히 표현하자면 자동 변수는 변수가 정의되어 있는 함수가 호출될 때마다 변수의 값을 보존하지 않는다는 것을 뜻한다. 지역 변수 x를 사용하는 함수가 프로그램에서 호출된다고 하자. 또한, 함수가 처음 호출될 때 변수 x에는 100의 값이 할당된다고 가정하자. 함수의 동작이 안료되면 제어는 다시 함수를 호출한 프로그램으로 돌아가고 함수는 나중에 다시 호출된다. 변수 x는 여전히 100의 값을 가지고 있을까? 그렇지 않다 처음에 생성되었던 변수 x는 제어가 프로그램으로 다시 전달될 때 사라졌다. 함수가 다시 호출될 때에는 변수 x가 새롭게 생성된다. 과거의 x는 완전히 사라진다. 그렇다면 함수가 호출될 때마다 지역 변수의 값을 보존해둘 필요가 있을 때에는 어떻게 할 것인가? 예를 들어, 데이터를 인쇄하는 함수는 새로운 페이지로 진행해야 하는 시기를 결정하기 위해서 이미 프린터로 전송된 줄(line)의 수를 기억할 필요가 있을 것이다. 함수가 호출될 때 계속해서 지역 변수의 값을 보존하기 위해서는 static 키워드를 사용하여 변수를 정적(static) 변수로 정의해야 한다. 예를 들어, 다음과 같이 할 수 있다.

   void func1( int x )
   {
      static int a;
      /* 추가적인 코드 */
   }

<리스트 12.4>에 있는 프로그램은 자도 변수와 정적 지역 변수 간의 차이를 보여준다.

<리스트 12.4> 자동 변수와 정적 지역 변수 간의 차이를 보여준다.

 /* 자동 변수와 정적 지역 변수의 사용 예 */

 #incude <stdio.h>


 void func1(void);


 main()

 {

    int count;


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

    {

       printf("At iteration l%d: ", count);

       func1();

    }


    return 0;

 }


 void func1(void)

 {

    static int x = 0;

    int y = 0;


    printf("x = %d, y = %d\n", x++, y++);

 }

프로그램은 다음과 같은 결과를 보여준다. 정적 변수로 선언된 x는 함수가 호출될 때 값을 보존하므로 변수의 값은 계속해서 증가한다. 반면에, 자도 변수로 선언된 y는 함수가 호출될 때마다 0으로 다시 초기화된다. 또한, 이 프로그램은 두 가지 형태의 변수에서 초기화가 수행되는 방법의 차이점을 보여준다. 즉, 변수가 정의되는 것과 초기화되는 시기에 대해서 알려준다. 정적 변수는 단지 함수가 처음 호출될 때에만 초기화된다. 나중에 다시 함수가 호출될 때 프로그램은 정적 변수가 이미 초기화되었다는 사실을 기억하고 있으므로 다시 초기화하지 않는다. 변수는 함수가 최근에 실행되었을 때 저장되어 있던 값을 그대로 가지게 된다. 이와는 대조적으로, 자동 변수는 함수가 호출될 때마다 지정된 값으로 다시 초기화된다. 자동 변수를 사용해보면 지금까지 설명한 내용을 충분히 이해할 수 있을 것이다. 예를 들어, <리스트 12.4>에 있는 프로그램에서 두 개의 지역 변수가 초기화되지 않도록 변경하면 함수 func1()은 다음과 같은 내용이 될 것이다.

  void func1(void)
  {
     static int x;
     int y;

     printf("x = %d, y = %d\n", x++, y++);
   }

프로그램을 이렇게 변경하여 실행하면 y의 값은 함수가 호출될 때마다 증가한다는 사실을 알 수 있다. 함수가 호출될 때 y의 값이 계속 보존된다는 것을 알 수 있는 것이다. 그렇다면 지금까지 자동 변수에 대해서 설명했던 내용은 잘못된 것일까? 절대로 그렇지 않다. 지금까지 설명한 내용은 모두 사실이다. 앞에서 설명한 내용과 프로그램의 실행 결과를 보고 알 수 있듯이, 함수가 반복적으로 호출될 동안 자동 변수가 값을 보존하는 것은 단지 우연한 일에 불과하다. 좀더 구체적으로 살펴보자. 함수가 호출될 때마다 새로운 y가 생성된다. 컴파일러는 이전에 함수가 호출되었을 때 사용하던 곳과 동일한 메모리 영역을 새로운 y에 할당하여 사용할 것이다. y가 함수 내에서 분명하게 초기화되지 않는다면 메모리의 저장 영역에는 y가 이전에 가지고 있던 값이 저장되어 있을수 있다. 그래서 변수는 계속해서 값을 보존하는 것처럼 보일 수 있지만, 사실은 우현한 일치에 불과한 것이다. 이렇게 같은 값을 가지게 되는 상황이 항상 발생한다고 볼 수는 없는 것이다.
 지역 변수는 기본적으로 자동 변수의 형태를 가지므로 변수를 정의할 때 지정할 필요가 없다 그러나 필요하다면 다음과 같이 변수의 데이터형을 표현하는 키워드 앞에 auto 키워드를 추가할 수 있다.

   void func1(int y)
   {
      auto int count;
      /* 그 밖의 프로그램 문장 */
   }

3.2 매개 변수의 범위
 : 함수의 헤더에서 매개 변수로 사용되는 변수는 지역(local) 변수와 같은 유효 범위를 가진다. 예를 들어, 다음 함수를 살펴보자.

   void func1(int x)
   {
      int y;
      /* 그 밖의 프로그램 문장 */
   }

 x와 y는 모두 함수 func1() 내에서만 유효하게 사용되는 지역 변수이다. 물론, x는 처음에 함수를 호출한 프로그램에서 전달되는 어떤 값을 가진다. 이렇게 전달된 값을 사용할 때에는 x를 다른 어떤 지역 변수와 같은 방법으로 사용할 수 있다. 매개 변수는 항상 대응하는 인수에 의해서 전달되는 값을 가지게 되므로 static이나 auto로 지정하는 것은 아무런 의미가 앖다.

3.3 외부 정적 변수
 : 외부 변수를 정의할 때 static 키워드를 포함시키면 정적 변수로 말들 수 있다.

    static float rate;
    main()
    {
      /* 그 밖의 프로그램 문장 */
    }

일반적인 외부 변수와 정적 외부 변수와의 차이는 유효 범위에 있다 .일반적인 외부 변수는 파일 내에 포함되어 있는 모든 함수에서 유효하고 다른 파일에 포함되어 있는 함수에 의해서도 사용될 수 있다. 정적 외부 변수는 단지 변수가 정의된 파일 내에서 변수가 정의된 부분 이후에 있는 함수에만 유효하다.

3.4 레지스터 변수
 : register 키워드는 자동 지역 변수가 메모리 대신에 프로세서의 레지스터에 저장되도록 컴파일러에게 지시하는 데 사용된다. 프로세서의 레지스터(processor register)는 무엇이고 레지스터를 사용할 때의 장점은 무엇일까? 컴퓨터의 CPU(central processing unit)에는 레지스터라는 몇 개의 데이터 저장 영역이 존재한다. 덧셈이나 뺄셈과 같은 실제 데이터 연산은 CPU의 레지스터에서 수행된다. CPU는 데이터를 처리하기 위해서 메모리 내에서 레지스터로 값을 읽어들이고 처리된 결과를 다시 메모리에 저장한다. 데이터를 메모리에서 읽어들이고 다시 메모리로 저장하는 일련의 과정에서는 약간의 시간이 소모된다. 만약 어떤 변수의 값을 레지스터에 저장하여 사용한다면 변수의 값을 더 빨리 사용할 수 있을 것이다.

자동 변수를 정의할 때 register 키워드를 포함시키면 변수를 레지스터에 저장하도록 '요쳥' 할 수 있다. 다음 예제를 살펴보자

   void runc1(void)
   {
      register int x;
      /* 그 밖의 프로그램 문장 */
   }

앞에서 '요청'이라는 단어를 사용했다는 것에 주의하기 바란다. 프로그램의 상태에 따라 변수의 값을 저장하기 위한 레지스터가 남아 있지 않을 수도 있다. 이때 컴파일러는 변수를 일반적인 레지스터 변수가 아니라 일반적인 자동 변수로 취급할 것이다. register 키워드는 명령하는 것이 아니라 일반적인 자동 변수로 취급할 것이다.

register 키워드는 명령하는 것이 아니라 '요청'하는 것이다 .레지스터 변수의 가장 큰 장점은 순환문의 카운터와 같이 함수 내에서 자주 사용되는 변수에서 증명된다. register 키워는 배열이나 구조체가 아니라 간단한 숫자 변수에 대해서만 사용될 수 있다. 또한 , 정적 변수나 외부 변수로 사용할 수 없다. 레지스터 변수에 대한 포인터를 정의하는 것도 불가능하다.

4. 지역 변수와 main() 함수
 : 지금가지 설명한 내용은 모두 main()과 다른 함수에 적용되는 사항이다. 정확히 말하면, main()은 다른 모든 함수와 마찬가지로 하나의 함수이다. main() 함수는 프로그램이 운영체제에서 실행될 때 호출되고 프로그램이 종료될 때 제어는 main()에서 운영체제로 돌아간다. 이것은 main()에서 정의된 지역 변수가 프로그램이 시작될 때 생성되고 프로그램이 종료될 때 사라진다는 것을 뜻한다. 그래서 main() 함수가 호출될 때 값을 보존하는 static 상태의 지역 변수를 생성하는 것은 아무런 의미가 없다.

변수는 프로그램이 실행될 때마다 값을 보존할 수 없다. 결과적으로, main() 함수에서는 자동 변수와 정적 지역 변수의 차이가 없다. main() 내에서는 물론 static 키워드를 사용하여 지역 변수를 정의할 수 있지만 아무런 효과를 나타내지 않는다.

# 잠깐 노트
 ·main()은 다른 모든 함수와 비슷한 구조를 가지는 하나의 함수라는 사실을 기억하자
 ·main() 내에서 정적 변수는 아무런 효과도 나타내지 않으므로 정적 변수를 생성한지 않도록 하자.

5. 어떤 형태의 변수를 생성해야 하는가?
 : 프로그램에서 특정 변수의 형태를 결정해야 할 때에는 다음 도표를 참고하면 도움이 될 것이다. <표 12.1>에는 C에서 사용할 수 있는 5가지 종류의 변수 형태가 나타나 있다.

<표 12.1> C 5가지 변수 형태

종 류

키워드

지속 시간

정의되는 위치

유효 범위

자동 변수

없음¹

일시적

함수 내부

지역적

정적 변수

static

일시적

함수 내부

지역적

레지스터 변수

register

일시적

함수 내부

지역적

외부 변수

없음²

지속적

함수 외부

전체적(모든 파일)

정적 외부 변수

static

지속적

함수 외부

전체적(특정 파일)

 ¹auto 키워드는 선택적으로 사용할 수 있다.

 ²extern 키워드는 다른 곳에서 이미 정의된 정적 외부 변수를 선언하기 위해서

   함수 내에서 사용되는 것이다.

변수의 형태를 결정할 때에는 가능하다면 자동 변수를 사용해야 하고, 필요한 경우에만 다른 형태를 선택해야 한다. 다음은 몇 가지 참고 사항이다.

·일단 모든 변수를 자동 지역 변수의 형태로 사용하자.
·변수가 자주 사용된다면 register 키워드를 사용하여 레지스터 변수로 정의하자.
·main()이 아닌 다른 함수 내에서 함수가 호출될 때 변수의 값이 보존되어야 한다면 정적 변수로 정의하자.
·변수가 프로그램의 대부분이나 또는 모든 함수에 의해서 사용된다면 외부 변수로 정의하자

6. 지역 변수와 블록
 : 지금까지 설명한 내용은 단지 함수 내에서 지역 변수를 사용하는 것을 중심으로 했다. 지역 변수는 기본적으로 함수 내에서만 사용되지만 중괄호 내에 포함된 프로그램의 어떤 블록 내에서도 지역적으로 사용되는 변수를 생성할 수 있다. 블록 내에서 사용되는 변수를 선언할 때에는 가장 먼저 변수가 선언되어야 한다는 것을 기억하자. 예를 들어, <리스트 12.5>를 살펴보자.

<리스트 12.5> 프로그램의 블록 내에서 지역 변수 정의하기

 /* 블록 내에서 지역 변수 사용하기 */


 #include <stdio.h>


 main()

 {

    /* main()에 지역적인 변수 정의 */


    int count = 0;


    printf("\nOutside the block, count = %d", count);


    /* 블록의 시작 */

    {

       /* 블록에 지역적인 변수 정의 */


       int count = 999;

       printf("\nWithin the block, count = %d", count);

    }


    printf("\nOutside the block again, count = %d\n", count);

    return 0;

 }

이렇게 지역 변수를 사용하는 것은 C 프로그래밍에서 흔하지 않고 필요하다고 생각되지도 않을 것이다. 실제로, 이렇게 같은 이름의 변수를 지역적으로 사용하는 방법은 프로그램에서 문제점을 찾는 경우에 가장 많이 사용된다. 즉, 프로그램의 일부분을 괄호 내에 포함시켜 일시적으로 독립시키고 잘못된 내용을 찾기 위해서 지역 변수를 사용할 수 있다. 다른 한 가지 장점은 변수가 사용되는 부분과 가까운 곳에서 변수를 선언하고 초기화할 수 있다는 것이다. 이렇게 하면 프로그램을 이해하기 쉽게 만들 수 있다.

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

10장 문자와 문자열  (0) 2019.06.02
11장 구조체  (0) 2019.06.02
13 장 고급 프로그램 제어문  (0) 2019.06.02
14장 화면, 프린터, 키보드 사용하기  (0) 2019.06.02
15장 포인터 : 고급 기능들  (0) 2019.06.02

+ Recent posts