문자(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

+ Recent posts