Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>
Docbook 원문

stdio.h 사용하기 (3)

1절. 소개

이번 기사는 stdio.h 사용하기stdio.h 사용하기 (2) 에 이은 stdio.h 사용에 관한 마지막 으로, 형식화된 입출력에 대해서 다루고 있다. 이번 기사는 다른 여러 기사와 마찬가지로 c 프로그래밍에 대한 기초적인 이해를 하고 있을것이라는 가정하에 만들어졌다.

형식화된 입출력은 Formatted Input(형식화된 입력) 과 Formatted Output (형식화된 출력)으로 나뉜다. 형식화된 입출력을 필요로 하는 곳은 파일을 통해서 읽어들인 데이타나 표준입력장치 를 통해서 받아들여진 데이타를 사용자가 보기 좋은 형태로 보여주기 위해서 주로 사용된다. 예를 들어

12345
657
845
1
		
이렇게 화면에 출력되는 것보다는
   12345
     657
     845
       1
		
이런식으로 오른쪽 정렬을 시켜줘야할 필요가 있는경우도 있을것이다. 혹은 시간을 나타낼적에 "년/월/시/분" 이런 식으로 나타내고 싶을 때도 있을것이다. 또는 stdin 으로 입력을 받거나, 파일등에서 입력을 받을때, 데이타가 "1234:yundream:011-524-3128" 이런식으로 입력되었다면, 이것을 ":" 을 기준으로 해서 포맷에 구애받지 않고 입력받고 싶을때도 있을것이다. 1234 는 int 형으로 yundream 은 char 형으로.. 이러한 작업을 쉽게 하기 위한 목적으로 형식화된 입출력 함수들을 사용할수 있다. 물론 이러한 작업을 token 을 기반으로한 포인터 노가다를 해도 가능하겠지만, stdio.h 에서 제공하는 함수들을 이용하면 훨씬 빠르고, 편하게 이러한 일들을 할수 있다.


2절. 형식화된 출력

사용자가 원하는 형식으로 값을 출력시켜주기 위해서 사용된다. 다음과 같은 printf 계열의 함수들을 이용한다.

#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const  char  *format, ...);
		
const char *format 는 뒤에 주어진 아규먼트들을 이용해서 형식화된 출력을 시켜준다. printf 가 stdout(표준출력)으로 형식화된 출력을 시키는 반면, fprintf 는 해당 FILE 객체로 형식화된 출력을 한다. 그러므로 printf(...) 는 fprintf(stdout,...) 와 동일한 일을 수행한다. sprintf 와 snprintf 는 char *str 로 형식화된 출력을 복사한다.

printf 계열의 함수는 형식화된 입출력을 지원하기 위해, 입력되는 변수의 type 에 대응되는 다양한 포맷문자 들을 지원한다. 이러한 포맷문자는 '%' 로 시작되며, 각 아규먼트와 순서대로 대응된다.

int  day=5;
char *mday="화";

printf("오늘은 %d일 %s요일 입니다", day, mday);
		
위의 예를 보면 day 와 mday 는 각각 %d 와 %s 로 출력됨을 알수 있다. %d 는 int 형의 숫자를 받아들이기 위해서 %s 는 문자열을 받아들이기 위해서 사용된다.

fprintf 는 출력을 stdout(표준출력-화면)으로만 가능한 printf 와는 달리 아규먼트로 주어지는 FILE 객체로 출력시킬수 있다.

int  day=5;
char *mday="화";

fprintf(stderr,"오늘은 %d일 %s요일 입니다", day, mday);
		
위의 예는 printf 버젼과 동일하지만, 출력을 표준에러로 보내고 있음을 알수 있다.

sprintf 와 snprintf 는 표준화된 출력의 내용을 다른 string 변수로 복사하기 위해서 사용한다.

int  day=5;
char *mday="화";
char today[255];

memset(today, 0x00, 255);
fprintf(today,"오늘은 %d일 %s요일 입니다", day, mday);
		
fprintf 와 매우 비슷하게 사용함을 알수 있다. fprintf 와 달리 표준화된 출력의 내용을 today 문자배열 변수로 보내고 있음을 알수 있다. sprintf 와 snprintf 의 차이점은 snprintf 가 복사될 문자열의 크기를 지정할수 있다는 점이다.


3절. 형식화된 입력

우리는 입력장치나 혹은 파일로 부터, 문자나 숫자를 입력받을수 있다. 그러나 보통의 read 를 쓸경우 읽어들인 데이타를 순수하게 문자열로 처리하므로, 숫자혹은 float 형으로 이것을 처리하고 싶을때는 꽤 복잡한 처리과정을 거쳐야 한다. 이럴때 scanf 계열의 형식화 입력 함수를 사용하면 간단하게 처리가 가능하다.

#include <stdio.h>
int scanf( const char *format, ...);
int fscanf( FILE *stream, const char *format, ...);
int sscanf( const char *str, const char *format, ...);
		
scanf 는 stdin(표준입력-키보드)로 부터 형식화된 입력을 받아들이고, fscanf 는 FILE 객체로 부터 받아들 인다. sscanf 는 const char *str 을 읽어들여서 형식화된 입력을 처리한다. scanf(..) 는 fscanf(stdin,..) 과 같은 일을 수행한다.
int int1, int2;
scanf("%d%d", &int1, &int2);
printf("%d %d", int1, int2);
		
위의 예는 2개의 정수를 stdin 으로 부터 입력받아서 int1 과 int2 에 각각 저장한다. scanf 계열의 함수는 장치나 파일로 부터 문자를 읽어들일때, 공백문자 즉 ('\n', ' ', '\t')를 기준으로 문자를 분리해서 받아들인다. 아래의 예를 컴파일 해서 입력테스트를 해보자
#include <stdio.h>

int main()
{
    float float1;
    int int1, int2;
    scanf("%d%d", &int1, &int2);

    printf("%d   %d", int1, int2);
}
		
값을 입력할때 "123"을 입력하고 "엔터키를 누르고" "45" 를 입력하면 int1과 int2 에 각각 123 과 45 가 입력될것이다. 혹은 "123 45" 이런식으로 중간에 공백문자를 두고 입력해도 동일한 효과를 가져올것이다. 이유는 공백문자를 기준으로 포맷변환을 하기 때문이다.

물론 우리는 공백이나 탭문자 대신에 "," 나 ":" 과 같은 단일문자를 구분자로 해서 형식화된 입력을 다룰수도 있다. 단일 문자를 구분자로 할경우에는 아래와 같이 단순하게 처리가 가능하다.

scanf("%d,%d", &int1, &int2);
printf("%d %d", int1, int2);
		
하지만 이런것 보다 좀더 복잡한 문자열 즉 ":=" 로 나눈다고 했을때는 위의 방법을 사용할수 없다. 대신 scanf 에서는 이러한 좀더 다양한 형식화된 입출력의 사용을 위해서 '[]'를 제공한다. 패턴매칭과 유사한 방식인 [a-z0-9] 이런식의 간단한 사용이 가능하다. 위의 ":=" 를 통한 형식화된 입출력을 원한다면
int int1, int2;
char dd[15];
scanf("%d%[=:]%d", &int1, &dd, &int); 
printf("%d - %d", int1, int2);
		
와 같은 방법을 사용하면 될것이다. 이걸 잘이용하면 좀더 복잡한 형식을 가진 입력을 받아들일 수도 있는데, 아래와 같은 방법을 사용하면 된다.
#include <stdio.h>

int main()
{
    int int1, int2;
    char dd[15];
    scanf("%d%[a-zA-Z]%d", &int1, &dd, &int);
    printf("number : %d - %d", int1, int2);
    printf("string : %s, dd);
}
		
위의 프로그램을 컴파일한후 실행시켜서 '1234AbCdEf5677' 이런식으로 입력을 해보자. 그러면 숫자와 문자열이 완전히 구분되어서 각각의 변수에 들어가게 됨을 확인할수 있을것이다.

또다른 예를 들어보자 입력받아서 처리해야 하는 데이타가 "aekf1234kefb"일경우 문자와 숫자를 각각 분리해서 받아내려면 어떻게 해야 할까 ?

char buf1[25];
char buf2[25];
int  int1;
scanf("%[a-zA-Z]%d%[a-zA-Z]", buf1, int1, buf2);
		
아주 간단하고 유연하게 형식화된 입출력이 가능함을 알수 있을것이다. 이러한 함수들을 이용하면 그동안 token 을 사용해서 복잡하게 작업했던 문자열 파싱 작업들을 매우 손쉽게 할수 있을것이다.


4절. 포맷변환 관련 문자들

4.1절. 타입변환

입력으로 받아들인 값 혹은 출력 값을 적당한 타입으로 변환하기 위해서 사용한다. 이러한 타입변환은 '%'를 이용해서 가능하며, '%' 뒤에 각각의 타입을 나타내는 문자를 사용하면 된다. 다음은 이러한 타입변환 문자들이다.

표 1. 변환문자

dint 
iint 
ounsigned 타입의 8진수 
uunsigned int 
xunsigned 타입의 16진수형태대문자로 표시된다 즉 13 은 1D
Xunsigned 타입의 16진수형태소문자로 표시된다 즉 13 은 1d
%%자체를 나타낸다. 
s스트링 point (char *) 
cunsigned char예를들어 65는 ASCII상에서 A를 가르킨다.
cunsigned char 
f소숫점을 가지는 double-999.99, 999.99
f소숫점을 가지는 double-999.99, 999.99
e부동소숫점을 가지는 double-999.99e+99, 9.9e-9
E부동소숫점을 가지는 double-999.99E+99, 9.9E-9


4.2절. 필드 정열

또한 stdio.h 의 함수들은 필드정렬을 통해서 보기좋은 출력결과를 만들수도 있다. 위의 "변환문자" 들과 조합해서 사용함으로써, 문자들을 필드 정열할수 있다. 변환 문자앞에 필드의 폭만큼 의 크기를 적어주면 된다. 정열은 기본적으로 오른쪽 정열이며, 지정된 폭크기 숫자 앞에 "-" 를 사용하면 왼쪽정렬 시킬수 있다. 또한 남는 필드를 '0' 값으로 채울수도 있다.

#include <stdio.h>
int main()
{
    printf("%8d%12s\t%04d\n", 1, "kim", 12);
    printf("%8d%12s\t%04d\n",  2, "yun", 56 );
}
			
위의 예는 아래와 같은 결과를 보여줄 것이다.
        1         kim    0012
        2         yun    0056