대부분의 프로그램은 입출력 동작을 수행한다. 프로그램이 입출력 동작을 얼마나 효과적으로 처리하는지는 가금 프로그램의 유용성을 판단하는 가장 좋은 기준이 된다. 앞에서는 이미 몇 가지 기본적인 입출력을 수행하는 방법을 배웠다. 오늘은 다음과 같은 내용을 배운다.

 ·C에서 입출력 동작을 수행하기 위해 스트림(stream)을 사용하는 방법
 ·키보드에서 입력되는 내용을 받아들이는 여러 가지 방법
 ·화면 상에 텍스트와 숫자 데이터를 출력하는 방법
 ·프린터로 출력하는 방법
 ·프로그램의 입력과 출력을 전환시키는 방법

1. 스트림과 C
 : 프로그램에서의 입출력에 대한 상세한 내용을 다루기 전에, 우선 스트림에 대해서 알아보도록 하자. C에서의 모든 입출력은 데이터가 어느 곳에서 입력되고, 어느 곳으로 출력되든지 관계없이 스트림을 통해서 수행된다. 나중에 설명하겠지만 모든 입력과 출력을 수행하는 이런 표준 방법은 프로그래머에게 상당한 이점을 제공한다. 물론, 이런 장점을 이용하기 위해서는 기본적으로 스트림이 무엇이고 어떻게 사용하는지 이해할 필요가 있다. 그러나 우선 입출력(input/output)이라는 용어에 대해서 정확히 알 필요가 있다.

1.1 프로그램의 입출력이 뜻하는 것
 : 이 강의의 앞부분에서 배웠듯이 C 프로그램이 실행되는 동안 데이터는 읽고 쓰기가 가능한 메모리(RAM)에 저장된다. 이렇게 메모리에 저장되는 데이터는 프로그램에서 선언된 변수, 구조체, 배열의 형태로 존재한다. 그러면 데이터는 어떻게 생성되고, 프로그램은 데이터를 사용하여 무엇을 수행할 수 있을까?

·데이터는 외부로부터 프로그램으로 전달될 수 있다. 외부에서 프로그램이 사용할 수 있는 RAM으로 데이터가 전달되는 것을 입력(input)이라고 한다. 프로그램에 대한 입력으로 가장  많이 사용되는 것은 키보드와 디스크 파일이다.

·또한, 데이터는 프로그램의 외부로 전달될 수 있다. 이것은 출력(output)이라고 한다. 출력의 대상으로 가장 많이 사용되는 것은 화면(모니터), 프린터, 디스크 파일이다. 입력을 제공하는 것과 출력의 대상이 되는 것을 장치(devices)라고 한다. 키보드, 화면 등은 하나의 장치이다, 키보드와 같은 장치는 입력을 위해서만 사용된다. 화면과 프린터 등은 출력을 취해서만 사용된다. 그리고 디스크 파일과 같은 것은 입력과 출력을 위해서 사용된다. 사용되는 장치가 무엇이고, 입력이나 출력 중에서 어떤 것을 수행하든지 관계없이 C에서 모든 입력과 출력은 스트림에 의해서 수행된다.

1.2 스트림이란?
 : 스트림(stream)은 일련의 문자들이다. 더욱 정확하게 말하자면 일련의 바이트로 구성된 데이터이다. 프로그램으로 전달되는 바이트는 입력 스트림이다. 프로그램에서 외부로 이동되는 바이트는 출력 스트림이다. 스트림이 어디로 가고 어디에서 오는지에 대해서 신경 쓸 필요는 없다. 스트림의 한 가지 중요한 장점은 프로그램의 입출력 동작이 장치에 독립적인 상태(device independent)로 수행된다는 것이다. 프로그래머는 키보드, 디스크 등 각각의 장치를 위한 특별한 입출력 함수를 작성할 필요가 없다. 프로그램은 어느 것에서 입력 동작이 수행되고, 어느 곳으로 출력 동작이 수행되든지 관계없이 연속적인 바이트의 스트림을 통해서 입출력을 처리한다. C의 모든 스트림은 파일에 관련되어 있다. 여기에서 파일(file)이라는 것은 디스크에 저장되는 파일을 뜻하는 것이 아니라 프로그램이 다루는 스트림과 입력이나 출력을 수행하는 실제 물리적 장치간의 중간 과정을 뜻한다. 대부분의 경우에는 스트림, 파일, 장치간의 상세한 동작이 C의 라이브러리 함수와 운영체제에 의해서 자동으로 다뤄지므로 초보 프로그래머는 이런 파일에 대해서 관심을 가질 필요가 없다.

1.3 텍스트 스트림과 이진 스트림
 : C의 스트림에는 텍스트 스트림과 이진 스트림의 두 가지 모드가 있다. 텍스트(text) 스트림은 화면에 출력되는 텍스트 데이터와 같은 문자만으로 구성된다. 텍스트 스트림은 255자까지의 길이를 가질 수 있고, 문장의 마지막을 나타내는 문자(EOF)나 문자 진행(newline) 문자에 의해서 종료되는 문장으로 구성된다. 텍스트 스트림을 구성하는 특정 문자는 문장 진행(newline) 문자와 같이 특별한 의미를 가지는 것으로 인식된다. 이장에서는 텍스트 스트림을 다룰 것이다. 이진(binary) 스트림은 텍스트 데이터로 제한되지 않고 모든 종류의 데이터를 다룰 수 있다. 이진 스트림에 포함되는 데이터는 어떤 특별한 방법으로 전송되거나 특별한 의미를 가지지 않는다. 데이터는 있는 그대로 읽어들여지거나 기록된다.

1.4 정의되어 있는 스트림의 종류
 : ANSI C에는 표준 입출력 파일(standard input/output files)로 취급되는 3개의 스트림이 정의되어 있다. DOS를 실행중인 IBM PC 호환 기종에서 프로그래밍한다면 두 개의 추가 스트림이 유효하다. 이런 스트림은 C 프로그램이 실행될 때 자동으로 열리고 프로그램이 종료될 때 닫힌다. 프로그래머는 스트림을 사용하기 위해서 추가로 어떤 동작을 수행할 필요가 없다. <표 14.1>에는 표준 스트림과 스트림에 연결되어 있는 장치들이 나타나 있다. 모든 표준 스트림은 텍스트 모드의 스트림이다.

<표 14.1> 5개의 표준 스트림

이 름

스트림

장 치

stdin

표준 입력

키보드

stdout

표준 출력

화면

stderr

표준 에러

화면

stdprn*

표준 프린터

프린터(LPT1:)

stdaux*

표준 보조

직렬 포트(COM1:)

 * DOS에서만 지원된다.

지금까지 화면 상에 텍스트를 출력하기 위해서 printf()나 puts() 함수를 사용할 때 항상 stdout 스트림을 사용했었다. 이와 비슷하게, 키보드의 입력을 읽어들이기 위해서 gets()나 scanf()를 사용할 때에는 stdin 스트림을 사용한다. 표준 스트림은 자동으로 열리지만, 디스크에 저장된 자료를 다룰 때 사용되는 것과 같은 스트림은 프로그래머가 직접 열어야 한다. 스트림을 여는 방법에 대해서는 낭중에 설명할 것이다. 여기는 표준 스트림에 대해서 설명할 것이다.

2. C의 스트림 함수
 : C의 표준 라이브러리에는 스트림의 입력과 출력을 다루는 다양한 함수가 포함되어 있다. 이런 함수들은 대부분 두 가지로 나누어진다. 하나는 항상 표준 스트림의 하나를 사용하는 것이고, 다른 하나는 프로그래머가 스트림의 종류를 지정하게 해주는 것이다. <표 14.2>에는 스트림 함수가 요약되어 있다. 여기에는 C의 모든 입출력 함수가 나타나 있지 않고, 여기서 설명할 모든 함수가 나타나 있는 것도 아니지만 스트림 함수를 대략적으로 살펴볼 수 있을 것이다.

<표 14.2> 표준 라이브러리의 스트림 입출력 함수

표준 스트림의

하나를 사용하는 것

스트림의 이름을

입력하도록 요구하는 것

수행하는 동작

printf()

fprintf()

 형식화된 출력

vprintf()

vfprintf()

 인수의 목록을 이용해서 형식화된 출력

puts()

fputs()

 문자열 출력

putchar()

putc(), fputc()

 문자 출력

scanf()

fscanf()

 형식화된 입력

gets()

fgets()

 문자열 입력

getchar()

getc(), fgetc()

 문자 입력

perror()


 stderr로 문자열 출력

대부분의 함수들은 STDLIB.H를 요구하고 함수 perror()도 STDLIB.H를 요구한다. 함수 vprintf()와 vfprintf()를 사용하려면 STDARGS.H를 포함시켜야 한다. UNIX 시스템에서 vprintf()와 vfprintf()를 사용하려면 VARARGS.H를 포함시켜야 한다. 추가로 헤더 파일이 필요한지 알아보려면 컴파일러의 라이브러리 레퍼런스를 참조하기 바란다.

2.1 예제 프로그램
<리스트 14.1>에 있는 간단한 프로그램은 스트림의 사용 예를 보여준다.

<리스트 14.1> 스트림의 특성을 보여주는 예제

 /* 스트림 입력과 출력의 동일성을 보여주는 예 */

 #include <stdio.h>


 main(0

 {

    char buffer[256];


    /* 한 줄을 입력받고 나서 그대로 출력 */


    puts(gets(buffer));


    return 0;

 }

=> 10번째 줄에 있는 gets() 함수는 키보드(stdin)에서 한 줄의 텍스트를 읽어들이는데 사용된다. gets()는 문자열에 대한 포인터를 돌려주므로 이 포인터를 화면(stdout)에 문자열을 출력하는 puts() 함수의 인수로 사용할 수 있다. 프로그램을 시앻하면 사용자에게서 한 줄의 텍스트를 입력받고 즉시 화면 상에 문자열을 출력한다.

3. 키보드 입력 받아들이기
대부분의 C 프로그램은 키보드, 즉 stdin에서 어떤 형태의 입력을 받아들인다. 입력 함수는 세 가지 종류로 나누어진다. 문자 입력, 한 줄의 텍스트 입력, 형식화된 입력.
 
3.1 문자의 입력
 : 문자 입력 함수는 지정된 스트림에서 한 번에 한 문자씩 입력을 받아들인다. 이런 함수를 호출하면 스트림 내의 다음 문자를 돌려주는데, 파일의 마지막에 도달했거나 에러가 발생핬다면 EOF를 돌려준다. EOF는 STDIO.H에서 -1로 정의되어 있는 기호 상수이다. 문자 입력 함수는 버퍼의 존재 여부와 반향 여부에 따라 구분된다.

·일부의 문자 입력 함수는 버퍼를 가진다(buffered). 이것은 enter키를 누를 때까지 입력된 문자를 운영체제가 임시 저장 영역에 보관해둔 상태에서 시스템이 문자를 stdin으로 보낸다는 것을 뜻한다. 다른 함수는 버퍼를 사용하지 않으며(unbuffered) 각각의 문자는 입력되는 즉시 stdin으로 전달된다.

·일부의 입력 함수는 각각의 문자가 입력될 때 자동으로 stdout에 반향하여 출력한다. 다른 함수는 문자를 반향하지 않는다. 이때, 문자는 stdout이 아니라 stdin으로 전달된다. stdout은 화면이므로 입력된 문자가 반향되어 나타난다. 버퍼의 사용 여부, 반향 여주에 따라 사용되는 문자 입력 함수를 살펴보자.

▶ getchar() 함수
 : 함수 getchar()은 스트림 stdin에서 다음 문자를 읽어들인다. 이 함수는 버퍼를 사용하고 문자 입력을 반향하며, 다음과 같은 원형을 가진다.

  int getchar(void);

getchar()의 사용 예는 <리스트 14.2>에 나타나 있다. 이 장의 후반부에서 설명할 putchar() 함수는 단순히 화면 상에 한 문자를 출력한다는 것을 기억하자.

<리스트 14.2> getchar() 함수의 사용 예  
 

 /* getchar() 함수의 사용 예 */


 #include <stdio.h>


 main()

 {

    int ch;


    while((ch = getchar()) != '\n')

       putchar(ch);


    return 0;

 }

=> 9번째 줄에서는 getchar() 함수를 호출하고 stdin에서 문자를 받아들이기 위해서 대기한다. getchar()는 버퍼를 사용하는 입력 함수이므로 Enter키를 누를 때까지는 아무런 문자도 읽어들이지 않는다. 그러나 입력되는 각각의 문자는 화면 상에 반향된다. Enter키를 누르면 문장 진행(newlint) 문자를 포함하여 입력된 모든 문자가 운쳧체제에 의해서 stdin으로 전달된다. getchar() 함수는 입력된 순서대로 한 번에 한 문자씩 ch에 할당하여 문자를 돌려준다. 각각의 문자는 문장 진행 문자 '\n'과 비교되고 문장 진행 문자가 아니라면 putchar()를 사용하여 화면상에 출력된다. getchar()가 문장 진행 문자를 돌려줄 때 while문이 종료된다.

getchar() 함수는 <리스트 14.3>에 나타나 있듯이 한 줄의 텍스트를 입력하는데 사용할 수 있다. 그러나 이렇게 한 줄의 텍스트를 입력하기 위해서는 이 장에서 나중에 설명할 다른 입력 함수를 사용하는 것이 좋다.

 <리스트 14.3> 한 줄의 텍스트를 입력하기 위해서 getchar() 함수를 사용하는 프로그램

 /* 문자열을 입력하기 위해 getchar() 사용 */


 #include <stdio.h>


 #define MAX 80


 main()

 {

    char ch, buffer[MAX + 1];

    int x = 0;


    while((ch == getchar()) != '\n' && x < MAX)

       buffer[x++] = ch;


    buffer[x] = '\0';


    printf("%s\n", buffer);


    return 0;

 }

▶ getch() 함수
 : getch() 함수는 스트림 stdin에서 다음 문자를 읽어들인다. 이 함수는 버퍼를 사용하지 않는 상태로 문자 입력을 반향 없이 수행한다. getch() 함수는 ANSI표준이 아니다. 즉, 이 함수는 모든 시스템에서 유효하지 않을 수 있고, 환경에 따라 다른 헤더 파일을 요구할 것이다. 일반적으로 getch()의 원형은 헤더 파일 CONIO.H에 다음과 같이 정의되어 있다.

  int getch(void);

 함수가 버퍼를 사용하지 않으므로 getch()는 Enter키를 누를 때가지 대기하지 않고 문자가 입력되는 즉시 각각의 문자를 돌려준다. getch()는 입력된 문자를 반향하지 않으므로 문자는 화면 상에 나타나지 않는다. <리스트 14.4>에 있는 프로그램은 getch()의 사용 예를 보여준다. <리스트 14.4> getch() 함수의 사용 예를 보여준다.

<리스트 14.4> getch() 함수의 사용 예

 /* getch() 함수의 사용 예 */

 /* ANSI 비호환 예제 */

 #include <stdio.h>

 #include <conio.h>


 main()
 {

    int ch;


    while((ch = getch()) != '\r')

       putchar(ch);


    return 0;

 }

-> 출 력

 Testing the getch() function

=> 이 프로그램을 실행하면 getch()는 키를 누르는 즉시 해당하는 문자를 돌려준다. 프로그램은 Enter키를 누를 때까지 대기하지 않는다. 반향이 없으므로 입력된 문자는 putchar() 함수에 의해서만 화면 상에 출력된다. getch()의 동작을 정확히 이해하기 위해서 10번째 줄의 마지막에 세미콜론을 추가하고, 11번째 줄인 putchar(ch)를 제거하자. 프로그램을 다시 실행할 때 입력한 문자가 아무 것도 화면에 반향되지 않음을 알 수 있을 것이다. getch() 함수는 화면에 문자를 반향하지 않고 읽어들인다. 원래의 리스트가 문자를 출력하기 위해 putchar()를 사용했으므로 화면에 나타난다는 것을 알 수 있다.

 이 프로그램에서는 왜 각각의 문자를 \n이 아니라 \r과 비교하는 것일까? 코드 \r은 개행 문자(carriage return)를 뜻하는 이스케[이프 시퀀스이다. Enter키를 누를 때 키보드는 stdin으로 개행 문자를 전달한다. 버퍼를 사용하는 문자 입력 함수는 개행 문자를 자동으로 문장 진행 문자로 변환하므로 프로그램에서는 Enter키의 입력을 확인하기 위해서 \n과 비교해야 한다. 그러나 버퍼를 사용하지 않는 입력 함수는 개행 문자를 문장 진행 문자로 변환하지 않으므로 개행 문자는 \r로 입력된다. 그래서 프로그램에서 개행 문자를 비교하는 것이다.

 한 줄의 텍스트를 입력하기 위해서 getch()를 사용하는 방법이 <리스트 14.5>에서 설명된다. 이 프로그램을 실행해보면 getch()가 입력된 문자를 반향하지 않는다는 사실을 분명히 알 수 있을 것이다. 이 프로그램은 getchar()가 getch()로 대치되어 있다는 것을 제외하고는 <리스트 14.3>과 동일하다.

<리스트 14.5> 한 줄을 입력하기 위한 getch() 함수의 사용 예

 /* 문자열을 입력하기 위해 getch(0 사용 */

 /* ANSI 비호환 예제 */

 #include <stdio.h>

 #include <conio.h>


 #define MAX 80


 main()

 {

    char ch, buffer[MAX + 1];

    int x = 0;


    while (( ch = getch()) != '\r' && x < MAX)

       buffer{x++] = ch;


    buffer[x] = '\0';


    printf("%s", buffer);


    return 0;

 }

-> 입력/출력
 Here's a string
 Here's a string

▶ getche() 함수
 : getche()는 각각의 문자를 stdout에 반향한다는 것을 제외하고는 getch()와 비슷하므로 쉽게 이해할 수 있을 것이다. getch() 대신에 getche()를 사용하기 위해서 <리스트 1.4.>에 있는 프로그램을 변경하자. 프로그램을 실행할 때 입력되는 각각의 키는 화면 상에 두 번 출력된다. 한 번은 getche()에 의해서 반향되는 것이고, 한 번은 putchar()에 의해서 출력되는 것이다.

▶ getc()와 fgetc() 함수
 : 문자 입력 함수 getc()와 fgetc()는 자동으로 stdin을 사용하지 않는다. 대신에, 프로그램에서 입력 스트림을 지정하게 해준다. 이 함수들은 나중에 상세히 설명할 디스크 파일에서 문자를 읽어들이는 경우에 기본적으로 사용된다.

▶ ungetc()를 사용하여 문자를 '되돌리는' 방법
 : 문자를 '되돌린다'는 것은 무엇을 뜻하는 것일까? 예를 들어 알아보도록 하자. 프로그램이 입력 스트림에서 문자를 읽어들이고 필요하지 않은 문자가 입력될 때 입력이 끝났다는 것을 인식한다고 가정하자. 예를 들어, 숫자만을 입력할 수 있는 프로그램에서는 숫자가 아닌 문자가 처음 나타날 때 이력이 종료된다는 것을 알 수 있을 것이다. 숫자가 아닌 문자는 전체적인 데이터에서 의미있는 내용이지만 입력 스트림을 통해서 받아들여진 후에 무시된다. 그렇다면 이 문자는 사라진 것일까? 그렇지 않다. 이 문자는 다음 입력 동작에서 첫 번째 문자로 읽어들일 수 있도록 스트림에 '되돌려지거나' 전달될 수 있다.

 문자를 '되돌리기' 위해서는 라이브러리 함수 ungetc()를 사용한다. 함수는 다음과 같은 원형을 가진다.

  int ungetc(int ch, FILE *fp);

인수 ch는 되돌려지는 문자이다. 인수 *fp는 문자를 되돋리는 대상 스트림을 지정하는 것으로 어떤 입력 스트림이든지 될 수 있다. 두 번째 인수로는 대개 stdin을 사용하면 된다. 즉, ungetc(ch, stdin);을 사용한다. FILE *fp는 디스크 파일과 관련된 스트림에서 사용되는 것으로 나중에 상세히 설명할 것이다.

 각각의 읽기 동작에서는 스트림에 단지 한 문자만을 '되돌릴' 수 있고 EOF 문자는 항상 '되돌려질' 수 없다. 함수 UNGETC()는 성공적으로 실행되면 ch를 돌려주고, 만약 문자를 스트림에 되돌릴 수 없으면 EOF를 돌려준다.

3.2 문장의 입력
 : 문장 입력 함수는 입력 스트림에서 한 줄의 문자열을 읽어들이는데, 문장 진행 문자까지의 모든 문자를 읽어들인다. 표준 라이브러리에서는 두 개의 문장 입력 함수 gets()와 fgets()가 제공된다.

▶ gets() 함수
 : 열번째 강의 "문자와 문자열"에서는 gets()함수를 소개했었다. 이 함수는 stdin에서 한 줄을 읽어들이고 문자열로 저장하는 간단한 함수이다. 함수의 원형은 다음과 같다.

  char *gets(char *str);

함수 원형은 쉽게 이해할 수 있을 것이다. gets()는 char형에 대한 포인터를 인수로 받아들이고 char형에 대한 포인터를 돌려준다. gets() 함수는 문장 진행 문자(\n)가 나타나거나 파일의 마지막에 도달할 때까지 stdin에서 문자를 읽어들인다. 문장 진행 문자는 널 문자로 대치되고 문자열은 str이 지적하는 영역에 저장된다.

 복귀값은 str과 동일하게 문자열에 대한 포인터이다. gets()를 수행할 때 에러가 발생하거나 도는 어떤 문자가 입력되기 전에 파일의 마지막에 도달하면 함수는 널 포인터를 돌려준다.

 gets()를 호출하기 전에는 문자열을 지정하기 위한 충분한 메모리 영역을 할당해야 한다. 함수는 str이 지적하는 부분에 저장 영역이 할당되었는지의 여부를 전형 알지 못한다. 문자열은 어떤 경우든지 입력되고 str에서부터 시작하여 저장된다. 만약 저장 영역이 할당되지 않았다면, 문자열은 다른 어떤 데이터를 대치하게 되므로 프로그램은 문제를 일으킬 것이다.

<리스트 10.5>와 <리스트 10.6>은 gets()를 사용한다.

▶ fgets() 함수
 : fgets() 라이브러리 함수는 입력 스트림에서 한 줄의 텍스트를 읽어들인다는 점에서 gets()와 비슷하다. 이 함수는 프로그래머가 사용하는 특정 입력 스트림과 입력되는 최대 문자의 수를 지정할 수 있게 해주므로 더욱 유용하다. fgets() 함수는 나중에 설명할 디스크 파일에서 텍스트를 읽어들이는 경우에 가금 사용된다. stdin에서 입력을 수행하는 경우에 이 함수를 사용하려면 입력 스트림으로 stdin을 지정해야 한다. fgets()의 원형은 다음과 같다.

  char *fgets(char *str, int n, FILE *fp);

마지막에 있는 매개 변수 FILE *fp는 입력 스트림을 지정하는 데 사용된다. 여기에서는 스트림 인수로 간단히 표준 입력 스트림인 stdin을 지정하자. 포인터 str은 입력 문자열이 저장되는 곳을 지적한다. 인수 n은 입력되는 최대 문자의 수를 나타낸다. fgets() 함수는 문장 진행 문자가 나타나거나, 파일의 마지막에 도달하거나, 또는 n - 1문자가 입력될 때까지 지정된 입력 스트림에서 문자를 읽어들인다. 입력된 문자열에는 \0이 추가되기 전에 문장 진행 문자가 포함된다. fgets()의 복귀값은 gets()에서 설명한 것과 동일하다. 정확히 말해서, 문장 진행 문자로 긑나는 일련의 문자를 한 줄의 텍스트라고 정의한다면 fgets()는 한 줄의 텍스트를 읽어들이지 않는다. 한 줄의 내용이 n - 1 문자보다 많은 것으로 구성된다면 함수는 전체적인 줄의 내용보다 적은 것을 읽어들일 것이다. stdin에서는 Enter키를 누를 때까지 fgets()에서 제어가 복귀되지 않고 처음에 입력되는 n - 1 문자라면 문장 진행 문자가 문자열에 포함된다. <리스트 14.6>에 있는 프로그램은 fgets() 함수의 사용 예를 보여준다.

<리스트 14.6> 키보드 입력을 받아들이기 위한 fgets()의 사용 예

 /* fgets() 함수의 사용 예 */


 #include <stdio.h>


 #define MAXLEN 10


 main()

 {

    char buffer[MAXLEN];


    puts("Enter text a line at a time; enter a blank to exit.");


    while(1)

    {

       fgets(buffer, MAXLEN, stdin);


       if(buffer[0] == '\n')

          break;


       puts(buffer);

    }

    return 0;

 }


3.3 형식화된 입력
 : 지금까지 설명한 입력 함수는 단순히 입력 스트림에서 하나 이상의 문자를 받아들이고 메모리 영역에 저장하는 것이었다. 입력된 문자는 변환되거나 형식화되지 않았으며, 숫자 변수에 대해서 여러 개의 문자를 입력하여 저장하는 방법도 없었다. 예를 들어, 12.86의 값을 키보드에서 입력하고 어떻게 float형 변수에 할당할 수 있을까? scanf()와 fscanf() 함수를 사용하면 된다. scanf()는 항상 stdin을 사용하는 반면에 fscanf()에서는 입력 스트림을 지정할 수 있다는 것을 제외하면 두 함수는 동일한 것이다. 여기에서는 scanf()를 다룰 것이다.

▶ scanf() 함수의 인수
 : scanf() 함수는 변칙적인 개수의 인수를 받아들인다. 함수는 최소한 두 개의 인수를 필요로 한다. 첫 번째 인수는 입력된 내용을 변환하는 방법을 scanf()에게 알려주기 위해서 특수한 문자를 사용하는 형식화 문자열이다. 두 번째 인수와 추가로 사용 가능한 인수는 입력된 데이터가 저장되는 변수의 주소이다. 다음은 예이다.

  scanf("%d", &x);

 첫 번째 인수 '%d'는 형식화 문자열이다. 여기에서 %d는 scanf()가 부호 있는 정수형값을 읽어들이도록 지시한다. 두 번째 인수는 입력된 값을 scanf()가 변수 x에 저장하도록 지시하기 위해서 주소(address of) 연산자인 &를 사용하고 있다. 이제, 형식화 문자열에 대해서 상세히 알아보도록 하자. scanf()의 형식화 문자열에는 다음과 같은 것이 포함될 수 있다.

·빈칸이나 탭 - 이것은 무시되지만 형식화 문자열을 더욱 읽기 쉽게 만들기 위해서 사용될 수 있다.

·%가 아닌 문자 - 이것은 입력되는 내용에서도 일치해야 하는 공백이 아닌 문자를 뜻한다.

·% 문자와 특수한 의미를 가지는 문자로 구성되는 하나 이상의 변환 문자(conversion specificaion) 일반적으로 형식화 문자열은 각각의 변수에 대응하는 하나의 변환 문자를 가지고 있다. 형식화 문자열에 반드시 포함되어야 하는 것은 변환 문자이다. 각각의 변한 문자는 백분율 기호(%)로 시작하고, 특별한 순서에 의해서 선택적으로 사용되거나 또는 반드시 포함되어야 하는 구성 요소를 가진다. scanf() 함수는 형식화 문자열에 포함되어 있는 변환 문자를 입력 필드에 순서대로 사용한다. 입력필드(input field)는 공백이 나타날 때 또는 폭이 지정되었다면 필드의 폭만큼 문자가 입력될 때 종료되는 공백이 아닌 일련의 문자들을 말한다. 변환 문자는 다음과 같이 구성된다.

·% 문자 바로 다음에 입력되는 선택적인 할당 제한(assignment suppression flag) 문자(*) - 이것은 scanf()가 현재의 변환 문자에 대응하는 변환을 수행하지만 결과를 무시하도록 지시한다. 즉, 값을 어떤 변수에 할당하지 않는다.

·선택적으로 사용되는 필드 폭(field width) - 필드 폭은 입력 필드의 폭을 문자 단위로 지정하는 값이다. 즉, 필드 폭은 scanf()에서 현재의 변환 동작을 수행하기 위해서 stdin에서 몇 개의 문자를 입력해야 하는지 지정한다 필드 폭을 지정하지 않으면 입력 필드의 폭은 다음에 나타나는 공백까지로 정해진다.

·선택적으로 사용되는 하나의 정밀도 지정 문자(precision modifier) - 이것은 h, l, L이 될 수 있다. 정밀도 지정 문자가 포함되면 형 지정자의 의미가 바뀐다.

·형 지정자(type specifier) - 이것은 변환 문자에서 % 외에 반드시 포함되어야 하는 하나의 구성 요소이다. 형 지정자는 scanf()가 입력 내용을 받아들이는 방법을 알려주는 하나 이상의 문자이다.

각각의 문자는 <표 14.3>에 나타나 있다. 인수는 대응하는 변수의 형태를 뜻한다. 예를 들어, 형지정자 d는 int형에 대한 포인터를 뜻하는 int *를 요구한다.

<표 14.3> 변환 문자에 포함되는 형 지정자

형태

인수

의미

d

int *

 10진 정수형

i

int *

 10진 정수형, 앞에 0을 포함하는 8진 정수형, 앞에 0x나 0X를

 포함하는 16진 정수형

o

int *

 앞에 0을 포함하거나 포함하지 않는 8진 정수형

u

unsigned int *

 부호 없는 10진 정수형

x

int *

 앞에 0x나 0X를 포함하거나 포함하지 않는 16진 정수형

c

char *

 하나 이상의 문자를 읽어들이고 인수에 의해서 지적되는 메모리

 영역에 순서대로 저장한다. \0은 추가되지 않는다.

 필드 폭이 지정되지 않으면 한 문자를 읽어들이고, 필드 폭이

 주어지면 공백을 포함하여 지정된 수의 문자를 읽어들인다.

s

char *

 공백이 아닌 문자로 구성되는 문자열을 지정된 메모리 영역에

 저장하고 \0을 추가한다.

e, f, g

float *

 부동 소수형 숫자. 숫자는 소수점 형태나 공학식 표기 방법으로

 입력할 수 있다.

[...]

char *

 문자열. 괄호 내에 포함되어 있는 문자만 입력된다. 입력 동작은

 일치하지 않는 문자가 나타나거나, 지정된 필드폭이 사용되거나

 Enter키를 누르는 즉시 종료된다. 문자 ]를 입력하기 위해서는

 []...]와 같이 처음에 위치시켜야 한다.

 문자열의 마지막에는 \0이 추가된다.

[^...]

char *

 괄호 내에 포함되지 않은 문자가 입력되는 것을 제외하면 [...]와

 동일하다.

%

없음

 문자 그대로의 % 문자를 읽어들인다. 할당 동작은 수행되지

 않는다.

scanf()의 사용 예를 살펴보기 전에 <표 14.4>에 나열된 정밀도 지정 문자에 대해서 이해할 필요가 있다.

<표 14.4> 정밀도 지정 문자

정밀도 지정 문자

의미

h

 문자 h는 형 지정자 d, i, n, o, u, x 앞에서 사용될 때 인수가 int형이

 아니라 short형에 대한 포인터라는 것을 지정한다. PC에서는 short형이

 int형과 동이하므로 정밀도 지정 문자 h는 전혀 필요하지 않다.

l

 문자 l은 형 지정자 d, i, n, o, u, x 앞에서 사용될 때 인수가 long형에

 대한 포인터라는 것을 지정한다. 문자 l은 형 지정자 e, f, g 앞에서

 사용될 때 인수가 double 형에 대한 포인터라는 것을 지정한다.

L

 문자 L은 형 지정자 e, f, g 앞에서 사용될 때 인수가 long double형에

 대한 포인터라는 것을 지정한다.

▶ 나머지 문자 다루기
 : scanf()에서 입력되는 내용은 벞에 저장된다. Enter키를 투를 때까지는 stdin에서 아무런 문자도 읽어들이지 않는다. Enter키를 누르면 전체 내용을 stdin에서 '읽어들이고' scanf()에서 순서대로 사용된다. scanf()의 형식화 문자열에 포함되어 있는 변환 문자에 일치하는 내용이 모두 입력되면 함수의 동작은 종료된다. 또한, scanf()는 형식화 문자열에서 지정된 변환 문자에 대응하는 내용만을 stdin에서 받아들인다, 추가 문자나 불필요한 문자가 있다면 stdin에서 '나머지로' 남게 된다. 이런 문자들은 문제를 일으킬 수 있는데, 어떤 문제가 발생하는지 알아보기 위해서 scanf()의 동작을 좀더 상세히 살펴보도록 하자. scanf() 함수가 호출되고 한 줄의 데이터를 입력할 때에는 다음과 같은 세 가지 상황이 발생할 수 있다. 우선, scanf("%d %d", &x, &y);라는 문장을 실행 중이라고 가정하자. 즉, scanf()는 두 개의 10진 정수형 값을 읽어들인다. 여기에서는 다음과 같은 상황이 발생할 수 있다.

·입력되는 내용이 형식화 문자열에 일치하는 경우
예를 들어, 12 14를 입력하고 Enter를 누르면 문제가 발생하지 않는다. scanf()는 필요한 내용을 받아들이고 stdin에는 나머지 문자가 존재하지 않는다.

·입력되는 내용이 형식화 문자열에서 요구하는 것보다 적은 요소를 포함하고 있는 경우
예를 들어, 12를 입력하고 Enter를 누르면 scanf()는 부족한 내용을 받아들이기 위해서 계속 대기한다. 일단 필요한 내용을 추가로 입력하면 실행이 계속되고 stdin에는 나머지 문자가 존재하지 않는다.

·입력되는 내용이 형식화 문자열에서 요구하는 것보다 많은 요소를 포함하고 있는 경우
예를 들어, 12 14 16을 입력하고 Enter를 누르면 scanf()는 12와 14를 읽어들이고 나서 프로그램으로 돌아간다. 나머지 문자인 1과 6은 stdin에서 남게 된다. 문제를 일으킬 수 있는 것은 세 번째 상황이고, 특히 나머지 문자들이 문제다. 이렇게 남은 문자들은 프로그램이 실행되는 동안 stdin에서 다시 입력을 받아들일 때까지 계속해서 남게 된다. 나중에 프로그램이 stdin에서 다시 입력을 받아들이면 나머지 문자는 다른 어떤 입력 내용보다 먼저 읽어들여진다. 이때 문제가 발생한다는 것은 쉽게 알 수 있다. 예를 들어, 다음 코드는 사용자에게 정수를 입력하고 나서 문자열을 입력하도록 요구한다.

  puts("Enter your age.");
  scanf("%d", &age);
  puts("Enter your first name.");
  scanf("%s", name);

 처음에 더욱 정확한 정수값을 입력하기 위해서 29.00을 입력하고 Enter를 누른다고 가정하자 첫 번째 scanf() 함수는 정수값을 받아들이므로 stdin에서 문자 29를 읽어들이고 변수 age에 할당한다. 문자 .00은 stdin에서 대기 상태로 남게 된다. 두 번째 scanf() 함수는 문자열을 받아들인다. 함수는 입력을 받아들이기 이해서 stdin을 사용하고, 대기중인 .00을 발견하게 된다. 결과적으로, 문자열 .00이 name에 저장된다.

 이 문제를 어떻게 방지할 수 있을까? 프로그램을 사용하는 사람이 이런 실수를 하지 않는다면 문제를 해결할 수 있을 것이다. 그러나 이것은 효과적인 에러 방지책이 아니다. 좀더 좋은 해결 방안은 입력 동작을 수행하기 전에 stdin에 문자가 남아 있지 않도록 해주는 것이다. stdin에서 문장의 마지막을 뜻하는 문자까지 모든 내용을 읽어들이는 gets() 함수를 호출하면 문제를 해결할 수 있다. 또한, 프로그램에서 gets()를 직접 호출하는 것보다는 좀더 해설적인 이름을 가지고 있는 clear_kb() 함수를 작성하는 것도 좋은 생각이다. <리스트 14.7>에서는 이 함수를 사용한다.

<리스트 14.7> 에러를 방지하기 위해서 stdin에서 나머지 문자를 제거하는 프로그램

 /* stdin에서 나머지 문자 제거 */


 #include <stdio.h>


 void clear_kb(void);


 main()

 {

    int age;

    char name[20];


    /* 사용자의 나이 입력 */


    puts("Enter your age.");

    scanf("%d", &age);


    /* stdin에서 나머지 문자 제거 */


    clear_kb();


    /* 사용자의 이름 입력 */


    puts("Enter your first name.");

    scanf("%s", name);

    /* 데이터 출력 */


    printf("Your age is %d.\n", age);

    printf("Your name is %s.\n", name);


    return 0;

 }


 void clear_kb(void)


 /* stdin에서 나머지 문자 제거 */

 {

    char junk[80];

    gets(junk);

 }

▶ fflush()로 나머지 문자 다루기
 : 입력된 나머지 문자를 정리할 수 있는 두 번째 방법이 있다. fflush() 함수는 표준 입력 스트림을 포함하여 특정 스트림에서 데이터를 지운다. fflush()는 일반적으로 디스크 파일에서 사용된다. 그러나 이 함수는 <리스트 14.7>을 더 간단히 만드는 데 사용될 수도 있다. <리스트 14.8>은 <리스트 14.7>에서 생성된 clear_kb() 함수 대신에 fflush() 함수를 사용하고 있다.

<리스트 14.8> fflush()를 사용하여 stdin에서 나머지 문자 지우기

 /* stdin에서 나머지 문자 제거 */

 /* fflush() 함수 사용 */

 #include <stdio.h>


 main()

 {

    int age;

    char name[20];


    /* 사용자의 나이 입력 */

    puts("Enter your age.");

    scanf("%d", &age);


    /* stdin에서 나머지 문자 제거 */

    fflush(stdin);


    /* 사용자의 이름 입력 */

    puts("Enter your first name.");

    scanf("%s", name);


    /* 데이터 출력 */

    printf("Your age is %d.\n", age);

    printf("Your name is %s.\n", name);


    return 0;

 }

▶ scanf()의 사용 예
 : scanf() 함수의 사용 방법을 익히는 가장 좋은 방법은 직접 사용해보는 것이다. 이 함수는 매우 강력하지만 처음에는 다소 혼란스러울 것이다. 함수를 사용하고 나타나는 결과를 살펴보자. <리스트 14.9>에 있는 프로그램은 scanf()를 사용하는 약간 독특한 방법을 보여준다. 이 프로그램을 컴파일하고 실행한 후에 scanf()의 형식화 문자열의 내용을 변경해가며 실험해보도록 하자.

<리스트 14.9> 키보드 입력을 받아들이기 위해서 scanf()를 사용하는 다양한 방법

 /* scanf()의 몇 가지 사용 예 */


 #include <stdio.h>


 main()

 {

    int i1, i2;

    long l1;


    double d1;

    char buf1[80], buf2[80];


    /* long 정수형과 배정도형을 입력하기 위해 사용 */


    puts("Enter an integer and a floating point number.");

    scanf("%ld %lf", &l1, &d1);

    printf("\nYou entered %ld and %lf.\n", l1, d1);

    puts("The scanf() format string used the l modifier to store");

    puts("your input in a type long and a type double.\n");


    fflush(stdin);


    /* 입력을 나누기 위해 필드 폭 지정 */


    puts("Enter a 5 digit integer (for example, 54321).");

    scanf("%2d%3d", &i1, &i2);


    printf("\nYou entered %d and %d.\n", i1, i2);

    puts("Note how the field width specifier in the scanf() format");

    puts("string split your input into two values.\n");


    fflush(stdin);


    /* 입력 내용을 두 문자열로 나누기 위해 형 지정자 사용 */


    puts("Enter your first and last names separated by a space.");

    scanf("%[^ ]%s", buf1, buf2);

    printf("\nYour first name is %s\n", buf1);

    printf("Your last name is %s\n", buf2);

    puts("Note how [^ ] in the scanf() format string, by excluding");

    puts("the space character, caused the input to be split.");


    return 0;

 }

4. 화면 출력
 : 화면 출력 함수는 입력 함수와 마찬가지로 대개 세 가지 종류로 구분된다. 즉, 문자 출력, 문장 출력, 형식화된 출력을 수행하는 함수가 있다. 앞에서는 몇 가지 함수를 설명했다. 여기에서는 좀더 상세히 살펴보도록 하자.

4.1 putchar(), putc(), fputc()를 사용한 문자 출력
 : C 라이브러리의 문자 출력 함수는 한 문자를 스트림으로 전달한다. 함수 putchar()는 일반적으로 화면을 뜻하는 stdout에 출력 내용을 보낸다. 함수 fputc()와 putc()는 출력 내용을 인수로 지정된 스트림으로 보낸다.

▶ putchar() 함수의 사용
 : putchar의 원형은 다음과 같이 STDIO.H에 정의되어 있다.

  int putchar(int c);

 함수는 c로 표현되는 문자를 stdout에 출력한다. 비록 원형이 int형 인수를 사용하도록 지시하고 있지만, 프로그램에서는 char형을 putchar()에 전달한다. 또한, 문자의 값이 0에서부터 255가지의 범위에 포함되는 것이라면 int형 값을 전달할 수 있다. 함수는 출력된 문자를 돌려주며, 에러가 발생한다면 EOF를 돌려준다. <리스트 14.2>에서는 이미 putchar()를 사용했었다. <리스트 14.10>에 있는 프로그램은 14와 127사이의 ASCII값을 가지는 문자를 출력한다.

<리스트 14.10> putchar() 함수

 /* putchar()의 사용 예 */


 #include <stdio.h>

 main(0

 {

    int count;


    for(count = 14; count < 128; )

       putchar(count++);


    return 0;

 }

문자열을 출력하기 위해서 다른 함수를 사용하는 것이 더 나을 수도 있겠지만 <리스트 14.11>에 나타나 있듯이 putchar() 함수를 사용할 수 있다.

<리스트 14.11> putchar()를 사용한 문자열 출력

 /* 문자열을 출력하기 위해 putchar() 사용 */


 #include <stdio.h>


 #define MAXSTRING 80


 char message[] = "Displayed with putchar().";

 main()

 {

    int count;


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

    {


       /* 문자열의 마지막을 검색하고 문장 진행 문자로 대치한 후 순환문을 마친다 */


       if(message[count] == '\0')

       {

          putchar('\n');

          break;

       }

       else


       /* 문자열의 마지막이 없으면 다음 문자를 추력한다. */

          putchar(message[count]);

    }

    return 0;

 }

▶ putc()와 fputc() 함수의 사용
 : 이런 두 함수는 한 문자를 지정된 스트림으로 전달하여 출력한다. putc()는 fputc()를 매크로의 형태로 사용하는 것이다. 매크로는 낭중에 "컴파일러의 고급기능"에서 설명할 것이다. 여기에서는 일단 fputc()만을 설명하겠다. 함수 원형은 다음과 같다.

  int fputc(int c, FILE *fp);

원형에서 FILE *fp라는 내용은 정확히 이해되지 않을 것이다. fputc()에서는 출력 스트림을 지정하는 인수로 사용된다. 만약 스트림으로 stdout을 지정하면 fputc()는 putchar()와 완전히 똑같이 사용된다. 그래서 다음 두 문장은 동일하다.

  putchar('x');
  fputc('x', stdout);

4.2 문자열 출력을 위한 puts()와 fputs()의 사용
 : 프로그램은 화면 상에 한 문자를 출력하는 것만큼이나 자주 문자열을 출력한다. 라이브러리 함수 putc()는 문자열을 출력한다. 함수 fputc()는 문자열을 지정된 스트림으로 전달하는데, 스트림을 지정한다는 것을 제외하고는 puts()와 동일하다. puts()의 원형은 다음과 같다.

  int puts(char *cp);

 *cp는 출력하기 원하는 문자열의 첫 번째 문자에 대한 포인터이다. puts() 함수는 문자열의 마지막 부분에 문장 진행 문자를 추가하고 종료를 뜻하는 널 문자를 포함하지 않은 상태로 전체적인 문자열을 출력한다. puts()는 성공적으로 실행되면 양수 값을 돌려주고, 에러가 발생하면 EOF를 돌려준다. EOF는 -1의 값을 가지는 기호 상수라는 것을 기억하자. 이 상수는 STDIO.H에 정의되어 있다. puts() 함수는 모든 형태의 문자열을 출력하는 데 사용될 수 있다. <리스트 14.12>는 함수의 사용 예를 보여준다.

<리스트 14.12> 문자열을 출력하기 위한 puts() 함수의 사용 예

 /* puts()의 사용 예 */


 #include <stdio.h>


 /* 포인터의 배열 선언과 초기화 */


 char *messages[5] = {"This", "is", "a", "short", "message."};


 main()

 {

    int x;


    for(x = 0; x < 5; x++)

       puts(messages[x]);


    puts("Add this is the end!");


    return 0;

 }

4.3 형식화된 출력을 수행하는 printf()와 fprintf()의 사용
 : 지금까지 설명한 출력 함수는 문자와 문자열만을 출력했다. 그렇다면 숫자를 출력하는 경우에는 어떻게 해야 할까? 숫자를 출력하기 위해서는 C 라이브러리의 형식화 출력 함수인 printf()와 fprintf()를 사용해야 한다. 또한, 이런 함수들은 문자열과 문자를 추력할 수도 있다 printf()는 지금까지 거의 모든 프로그램에서 사용했다. 여기에서는 좀더 상세한 내용을 알아보도록 하자.

 printf()가 항상 stdout으로 출력하는 반면에 fprintf()는 출력 스트림을 지정할 수 있다는 것을 제외하면 두 함수 printf()와 fprintf()는 동일하다. printf() 함수는 변칙적인 개수의 인수를 받아들이고 최소한 하나의 인수를 필요로 한다. 반드시 포함되어야 하는 첫 번째 인수는 printf()에게 출력을 형식화하는 방법을 알려주는 형식화 문자열(format string)이다. 선택적으로 사용되는 인수는 값을 출력하기 원하는 변수와 수식이다. printf()에 대한 상세한 내용을 다루기 전에 기본적인 개념을 이해하기 위해서 몇 가지 간단한 예를 살펴보도록 하자.

·printf("Hello, world.");라는 문장은 메시지 Hello, world.를 화면 상에 출력한다. 이 것은 단지 하나의 인수인 형식화 문자열만을 포함하고 잇는 printf()를 사용하는 예이다. 여기에서는 형식화 문자열이 화면 상에 출력되는 문자 그대로의 문자열을 가지고 있다.

·printf("%d", i);라는 문장은 화면 상에 정수형 변수 i의 값을 출력한다. 형식화 문자열은 printf()에게 하나의 십진 정수형 값을 출력하도록 지시하는 변환 문자 %d만을 포함하고 있다. 두 번째 인수인 i는 값을 출력할 변수의 이름이다.

·printf("%d plus %d equals %d.", a, b, a + b);라는 문장은 a와 b가 각각 2와 3의 값을 가지는 정수형 변수라고 가정했을 때 화면 상에 2 plus 3 equals 5를 출력한다. 여기에서는 printf()가 4개의 인수를 가지고 있다. 즉, 변환 문자와 문자 그대로의 텍스트를 가지는 형식화 문자열, 두 개의 변수, 합을 출력하기 위한 수식이다. 이제 printf() 형식화 문자열에 대해서 좀더 상세히 살펴보도록 하자. 형식화 문자열에는 다음과 같은 내용이 포함될 수 있다.

·printf()에게 인수 목록에 있는 값을 출력하는 방법을 알려주는 하나 이상의 변환 문자. 변환 문자는 %와 하나 이상의 문자로 구성된다.

·변환 문자가 아닌 있는 그대로 출력되는 문자

 앞에서 세 번째 예제에 포함된 형식화 문자열은 %d plus %d equals %d이다. 여기서 세 개의 %d는 변환 문자이고, 공백을 포함한 문자열의 나머지 부분은 그대로 출력되는 문자이다. 이제, 변환 문자에 대해서 알아보자. 변환 문자의 형식은 다음과 같고 자세한 내용은 잠시 후에 설형할 것이다. 대괄호 내에 포함되어 있는 구성 요소는 선택적으로 사용할 수 있는 것들이다.

  %[flag][field_width][.[precision]][l]conversion_char

 conversion_char는 변환 문자에서 %와 함께 반드시 포함되어야 하는 내용이다. 변환 문자의 의미는 <표 14.5>에 나타나 있다.

<표 14.5> printf()와 fprintf()의 변환 문자

변환 문자

의    미

d, i

 부호 있는 10진 정수형 출력

u

 부호 없는 10진 정수형 출력

o

 부호 없는 8진 정수형 출력

x, X

 부호 없는 16진 정수형 출력. x는 소문자로 출력하고 X는 대문자로 출력한다.

c

 하나의 문자 출력. 문자의 ASCII코드가 인수로 사용된다.

e, E

 공학식 표기로 float나 double형 출력. 예를 들어, 123.45는 1.234500+002로

 출력된다. 잠시 후에 설명할 정밀도를 지정하지 않으면 소수점 아래 6자리

 까지 출력된다. 출력에서 대소문자를 지정하기 위해서 e나 E를 사용하자.

f

 소수점 표기로 float나 double형 출력. 예를 들어, 123.45는 123.450000으로

 출력된다. 정밀도를 지정하지 않으면 소수점 아래 6자리까지 출력된다.

g, G

 e, E, f에서 하나를 사용한다. 만약 지수가 -3보다 작거나 또는 기본적으로

 6에 설정된 정밀도보다 크다면 e나 E 형식이 사용된다. 그렇지 않다면

 f 형식이 사용된다. 숫자값 뒤에 나타나는 의미 없은 0은 제거된다.

n

 아무 것도 출력하지 않는다. 변환 문자 n에 대응하는 인수는 int형에 대한

 포인터이다. printf() 함수는 이 변수에 지금까지 출력된 문자의 수를 할당한다.

s

 문자열 출력. 인수는 char에 대한 포인터이다. 널 문자가 나타나거나 기본적

 으로 32,767에 설정되어 있는 precision을 다르게 지정한다면 그만큼의

 문자가 출력될 때까지 문자가 출력된다. 마지막의 널 문자는 출력되지 않는다.

%

 문자 % 출력

변환 문자 앞에는 형 지정 문자(modifier) 1을 포함시킬 수 있다. 이 문자는 변환 문자 o, u, x, X, i, d, b에만 적용된다. 이 지정 문자는 인수가 int형이 아니라 long형의 정밀도를 가진다는 것을 지정한다. 문자 1이 변환 문자 e, E, f, g, G에서 사용되면 인수가 double형의 정밀도를 가진다는 것을 지정한다. 1을 다른 어떤 변환 문자 앞에서 사용하면 무시된다.

 정밀도 지정 문자는 소수점(.)과 숫자로 구성된다. 정밀도 지정 문자는 단지 변환 문자 e, E, f, g, G, s에서만 사용할 수 있는데, 소수점 아래에 출력되는 숫자의 자릿수를 지정하거나 또는 s에서 사용될 때에는 출력되는 문자의 수를 지정한다. 만약 소수점만 사용되면 정밀도가 0이라는 것을 나타낸다. 필드 폭 지정자는 출력되는 문자의 폭을 결정한다. 필드 폭 지정자에는 다음과 같은 것을 사용할 수 있다.

·0으로 시작하지 않는 십진 정수형 값
  출력되는 내용의 왼쪽 부분에는 지정된 필드 폭을 채우기 위해서 공백이 포함된다.

·0으로 시작하는 십진 정수형 값
  출력되는 내용의 왼쪽 부분에는 지정된 필드 폭을 채우기 위해서 0이 포함된다

·*문자
 int형의 다음 인수의 값을 필드 폭으로 사용한다. 예를 들어, w가 10의 값을 가지는 int형 변수라면 printf("%*d", w, a);라는 문장은 필드 폭이 10인 상태로 a의 값을 출력한다

필드 폭이 지정되지 않거나 또는 지정된 필드 폭이 출력 내용보다 좁다면 출력되는 내용은 필요한만큼 확장된 필드 폭을 사용하게 된다.

 printf()의 형식화 문자열에서 선택적으로 사용되는 마지막 부분은 문자 % 바로 다음에 나타나는 플래그(flag)이다. 네 개의 플래그를 사용할 수 있다.

·- : 출력되는 내용을 출력 필드 내에서 왼쪽 기준으로 정렬한다. 기봊적으로는 오른쪽 기준으로 정렬하도록 되어 있다.

·+ : 부호 있는 숫자를 출력할 때 항상 +나 -와 같은 부호를 함께 출력한다.

·`' : 양수를 출력할 때 앞부분을 빈칸으로 채워서 나타낸다.

·# : 변환 문자 x, X, o에만 적용된다. 0이 아닌 숫자를 x나 X에서는 0X나 0x와 함께 출력하고, o에서는 0과 함께 출력한다.

printf()를 사용할 때 형식화 문자열은 printf()의 인수 목록에서 큰 따옴표 내에 포함되어 있는 문자 그대로의 문자열이 될 수 있다. 또한 printf()에 문자열에 대한 포인터를 전달할 때에는 널 문자를 가지고 있으며, 메모리에 저장되어 있는 문자열을 사용할 수 있다. 예를 들어, 다음 문장은

  char *fmt = "The answer is %f.";
  printf(fmt, x);

다음과 같다.

  printf("The answer is %f.", x);

printf()의 형식화 문자열은 출력되는 내용을 제어하게 해주는 이스케이프 시퀀스를 포함할 수 있다. <표 14.6>에는 가장 자주 사용되는 이스케이프 시퀀스가 나타나 있다. 예를 들어, 형식화 문자열에 문장 진행 문자(\n)를 포함시키면 그 다음의 내용은 다음 줄부터 시작하여 출력된다.

<표 14.6> 가장 자주 사용되는 이스케이프 시퀀스

부호

의미

\a

 경고음(bell)

\b

 백스페이스(backspace)

\n

 문장 진행(newline)

\t

 수평 탭

\\

 백슬래시

\?

 물음표

\'

 작은 따옴표

\"

 큰 따옴표

printf()는 상당히 복잡한 함수이다. 이 함수의 사용법을 익히는 가장 좋은 방법은 예제를 살펴보고 직접 사용해보는 것이다. <리스트 14.13>에 있는 프로그램은 prntf()를 사용할 수 있는 다양한 방법을 보여주고 있다.

<리스트 14.13> printf() 함수를 사용하는 다양한 방법

 /* printf()의 사용 에 */


 #include <stdio.h>


 char *m1 = "Binary";

 char *m2 = "Decimal";

 char *m3 = "Octal";

 char *m4 = "Hexadecimal";


 main()

 {

    float d1 = (float) 10000.123;

    int n = 0;


    puts("Outputting a number with different field widths.\n");


    printf("%5f\n", d1);

    printf("%10f\n", d1);

    printf("%15f\n", d1);

    printf("%20f\n", d1);

    printf("%25f\n", d1);


    puts("\n Press Enter to continue...");

    fflush(stdin);

    getchar();


    puts("\nuse the * field width specifier to obtain field width");

    puts("from a variable in the argument list.\n");


    for(n = 5; n <= 25; n += 5)

       printf("%*f\n", n, d1);


    puts("\n Press Enter to continue...");

    fflush(stdin);

    getchar();


    puts("\nInclude leading zeros.\n");


    printf("%05f\n", d1);

    printf("%010f\n", d1);

    printf("%015f\n", d1);

    printf("%020f\n", d1);

    printf("%025f\n", d1);


    puts("\n Press Enter to continue...");

    fflush(stdin);

    getchar();


    puts("\nDisplay in octal, decimal, and hexadecimal.");

    puts("use # to precede octal and hex output with 0 and 0X.");

    puts("use - to left-justify each value in its field.");

    puts("First display column labels.\n");


    printf("%-15s%-15s%-15s", m2, m3, m4);


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

       printf("\n%-15d%-#15o%-#15x", n, n, n);


    puts("\n Press Enter to continue...");

    fflush(stdin);

    getchar();


    puts("\n\nuse the %n conversion command to count characters.\n");


    printf("%s%s%s%s%n", m1, m2, m3, m4, &n);


    printf("\n\nThe last printf() output %d characters.\n", n);


    return 0;

 }

5. 입력과 출력의 전환
 : stdin과 stdout을 사용하는 프로그램에서는 방향 전환(redirection, 또는 재지정)이라는 운영체제의 특성을 이용할 수 있다. 전환 기능을 통해서는 다음과 같은 결과를 얻을 수 있다.

·stdout으로 출력되는 내용을 화면이 아니라 디스크 파일이나 프린터로 전달할 수 있다.

·stdin을 사용하는 프로그램의 입력 동작을 키보드가 아니라 디스크 파일에서 수행할 수 있다.

 이런 전환 기능은 프로그램에서 수행되는 것이 아니라 프로그램을 실행할 때 DOS의 명령 라인(command line)에서 지정된다. DOS와 UNIX에서 입출력을 전환시킬 때 사용하는 기호는 >와 <이다. 우선 출력의 전환에 대해서 알아보도록 하자.

 여기서 처음 소개한 HELLO.C 프로그램을 기억할 수 있는가? HELLO>C는 'Hello, world'라는 내용을 화면 상에 출력하기 위해서 라이브러리 함수 printf()를 사용했었다. 지금까지 설명한 내용에 의하면 printf()는 출력 내용을 stdout으로 전달하므로 출력을 전환시키는 것이 가능하다. 명령 프롬프트에서 프로그램의 이름을 입력할 때 > 기호와 새로운 출력 장치의 이름을 입력하면 된다.

  hello > destination

hello > prn을 입력하면 프로그램의 출력은 화면이 아니라 프린터를 통해서 나타난다. prn은 DOS에서 사용되는 포트 LPT1:에 접속된 프린터의 이름이다. hello > hello.txt를 입력하면 출력 내용은 hello.txt라는 이름의 디스크 파일에 저장될 것이다.

 출력을 디스크 파일로 전환시킬 때에는 주의해야 한다. 지정된 파일이 이미 존재한다면 이전에 저장되어 있던 내용은 삭제되고 새로운 내용의 파일로 바뀔 것이다. 파일이 존재하지 않는다면 새롭게 생성된다. 또한, 출력을 파일로 전환시킬 때에는 >> 기호를 사용할 수도 있다. 이 기호는 지정된 파일이 이미 존재할 때 프로그램의 출력 결과를 파일의 마지막 부분에 추가한다. <리스트 14.14>에 있는 프로그램은 방향 전환의 예를 보여준다.

<리스트 14.14> 입력과 출력을 전환시키는 예를 보여주는 프로그램

 /* stdin과 stdout의 재지정 예제 */


 #include <stdio.h>


 main()

 {

    char buf[80];


    gets(buf);

    printf("The input was: %s\n", buf);

    return 0;

 }

5.1 입력의 전환
 : 이제, 입력의 전환에 대해서 알아보자. 우선, 자료 파일이 필요하다. 에디터를 사용하여 William Shakespeare라는 데이터를 가지는 INPUT.TXT라는 이름의 파일을 생성하자. 다음과 같은 내용을 DOS 프롬프트에서 입력하여 <리스트 14.14>를 실행하자.

  list1414 < INPUT.TXT

프로그램은 키보드에서 데이터를 입력하도록 요구하지 않을 것이다. 대신에, 실행되는 즉시 화면 상에 메시지를 출력한다.

  The input was: William Shakespeare

스트림 stdin은 디스크 파일인 INPUT.TXT로 전환되었으므로, 프로그램의 gets() 함수는 키보드가 아니라 지정된 파일에서 한 줄의 텍스트를 읽어들인다. 또한, 입력과 출력을 동시에 전환시킬 수도 있다. stdin을 파일 INPUT.TXT로 지정하고, stdout를 JUNK.TXT로 지정하기 위해서 다음 명령을 입력하여 프로그램을 실행해보자.

  list1414 < INPUT.TXT > JUNK.TXT

stdin과 stdout을 전환시키는 것은 특별한 상항에서 아주 유용할 것이다. 예를 들어, 정렬(sorting) 프로그램은 키보드에서 입력되는 내용이나 디스크 파일에 저장된 데이터를 모두 분류할 수 있을 것이다. 비슷하게, 우편용 주소록 관리 프로그램은 화면 상에 주소를 출력하고 우편용 레이블을 인쇄하기 위해서 프린터로 결과를 전달하거나 또는 다른 목적을 위해서 파일에 저장할 수도 있을 것이다.

6. fprintf()를 사용하는 경우
 : 앞에서도 언급했듯이, 라이브러리 함수 fprintf()는 출력 내용이 전달되는 스트림을 지정할 수 있다는 것을 제외하면 printf()와 동일하다. fprintf()의 용도는 주로 디스크 파일과 관련되어 있다. 여기에서는 두 가지 다른 용도를 알아보도록 하자.

6.1 stderr의 사용
 : Cㅇ서 정의되어 있는 스트림의 하나는 표준 에러인 stderr이다. 프로그램의 에러 메시지는 일반적으로 stdout이 아니라 stderr로 전달된다. 왜 그럴까? 방금 설명했듯이 stdout에 대한 출력 동작은 화면이 아닌 다른 곳으로 변경되어 수행될 수 있다. 에러 메시지가 stdout으로 출력된다면 stdout을 전환시킬 때 프로그램이 출력하는 어떤 에러 메시지도 볼 수 없을 것이다. stdout과 달리 stderr은 전환될 수 없고 항상 화면에 연결되어 있다. 적어도, DOS에서는 그렇다. UNIX 시스템은 stderr의 방향 전환을 허용할 것이다. 그래서 DOS에서는 에러 메시지를 stderr로 출력하면 사용자가 항상 에러 메시지를 볼 수 있는 것이다. 이것은 fprintf()를 사용하여 수행된다.

  fprintf(stderr, "An error has occurred.");

프로그램에서는 fprintf()를 직접 사용하지 않고 에러를 처리하는 함수를 작성하고 나서 에러가 발생할 때 함수를 호출할 수 있다.

  error_message("An error has occurred.");
     void error_message(char *msg)
     {
        fprintf(stderr. msg);
     }

fprintf()를 직접 호출하지 않고 함수를 만들어 사용하면 구조화 프로그래밍의 한 가지 장점인 추가적인 융통성을 발휘할 수 있다. 예를 들어, 특정 상황에서는 프로그램의 에러 메시지가 프린터나 디스크 파일로 출력되기 원할 것이다. 이럴 때 원하는 곳에서 출력 동작을 수행하도록 error_message() 함수를 변경하면 된다.

6.2 DOS에서의 프린터 출력
 : DOS나 윈도우 시스템에서는 프린터로 출력 동작을 수행하기 위해서 이미 정의되어 있는 스트림 stdprn을 사용한다. IBM PC와 호환 기종에서는 스트림 stdprn이 LPT1:에 접속되어 있다. LPT!:은 첫 번째 병렬 프린터 포트이다. <리스트 14.15>는 간단한 예를 보여준다.

<리스트 14.15> 출력을 프린터로 전달하기

 /* 프린터 출력의 예 */


 #include <stdio.h>


 main()

 {

    float f = (float) 2.0134;


    fprintf(stdprn, "\nThis message is printed.\r\n");

    fprintf(stdprn, "Add now some numbers:\r\n");

    fprintf(stdprn, "The square of %f is %f.", f, f * f);


    /* 종이 넘김(폼피드) */

    fprintf(stdprn, "\f");


    return 0;

 }

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

12장 변수의 범위  (0) 2019.06.02
13 장 고급 프로그램 제어문  (0) 2019.06.02
15장 포인터 : 고급 기능들  (0) 2019.06.02
16장 링크리스트  (0) 2019.06.02
17장 디스크 파일의 사용  (0) 2019.06.02

+ Recent posts