-
03. Formatted Input/Output프로그래밍 언어/C 2020. 3. 19. 21:33
03. Formatted Input/Output
In seeking the unattainable, simplicity only gets in the way.
`printf`와 `scanf`는 formatted reading and writing을 지원한다. [Section 3.1](##01. the `printf` function)에서는 `printf`를, [Section 3.2](##02. the `scanf` function)에서는 `scanf`를 설명한다.
01. The `printf` Function
`printf`는 문자열의 특정 위치에 값을 삽입하는 ***formatted string***의 출력을 위해 고안된 함수다.
`printf(`*format string*`, `*expr1*`, `*expr2*`, `*...*`);`
format string은 일반적인 문자들과 `%`로 시작하는 ***conversion specifications***이 포함된다. 일반 문자는 출력할 때 그대로 출력 줄에 복사되고 conversion specifications는 값들을 대표하는 place holder 역할을 하면서 출력할 때 값들로 채워진다. conversion specifications는 2진수 형태의 값을 어떤 형식으로 *convert*할지 *specifies*하게 된다 (그래서 conversion specifications라고 부른다).
Conversion Specifications
Conversion specifications는 프로그래머에게 출력의 결과를 쉽게 컨트롤 할 수 있게 해주지만 복잡하고 읽기 어렵다는 측면도 있다. conversion specifications는 `%m.pX` 또는 `%-m.pX`의 형식을 갖는다. 여기서 `m`과 `p`는 정수를 `X`는 문자를 나타낸다.
m은 ***minimum field width***를 나타내고 만약 출력할 수의 자릿수가 m 보다 작다면 오른쪽 정렬해서 출력한다. 만약 m 앞에 `-`가 붙는다면 왼쪽 정렬로 출력한다. p는 ***percision***이고 X에 따라 의미가 달라진다. 가장 많이 사용되는 X에 대한 의미를 살펴보자면 다음과 같다.
-
`d` (십진수 정수 형태): 출력할 때 출력할 최소 자리수를 나타낸다. 출력할 수의 자릿수가 p 보다 작다면 앞에 0을 붙여서 출력한다. 만약 p가 생략된다면 1이라고 가정한다. (`%d = %.1d`)
-
`e` (exponential 형태의 실수): 출력할 때 소숫점 후 몇 자리나 출력할지 결정한다. 생략된다면 6으로 가정하고 만약 p가 0이라면 소숫점은 출력되지 않는다.
-
`f` (10진수 형태의 실수): *exponential* 없이 십진수 실수로 출력하고 p의 역할은 `e` specifier과 같다.
-
`g` (수의 크기에 따른 exponential 형태의 실수 또는 10진수 형태의 실수): p는 출력할 의미있는 자릿수의 개수를 정한다. `f`와 다른 점은 소숫점 아래 수가 없다면 소숫점을 표시하지 않는다.
특히 `g` specifier은 출력할 수의 크기를 예측할 수 없거나 크기가 다양할 때 유용하게 사용할 수 있다.
`%d`, `%e`, `%f`, `%g` 말고도 수많은 specifiers가 있다. 전체 목록은 Section 22.3에서 설명한다.
Program: conversion_specifier.c
#include <stdio.h> int main(void) { int seoul = 42; float fortytwo = 1764.4242; printf("%%4d = %4d\n", seoul); printf("%%04d = %04d\n", seoul); printf("%%-4d = %-4d\n", seoul); printf("\n"); printf("%%.7d = %.7d\n", seoul); printf("%%.7e = %.7e\n", fortytwo); printf("%%.7f = %.7f\n", fortytwo); printf("%%.7g = %.7g\n", fortytwo); return (0); }
output:
%4d = 42$ %04d = 0042$ %-4d = 42 $ $ %.7d = 0000042$ %.7e = 1.7644242e+03$ %.7f = 1764.4241943$ %.7g = 1764.424$
Escape Sequences
`\n`과 같은 코드를 ***escape sequences***라고 부른다. Escape sequences는 컴파일러에게 특별한 의미가 있는 `"`과 같은 문자를 문자열에 포함시켜 출력할 수 있게 해준다.
NameEscape Sequence
Alert \a Backspace \b Form feed \f New line \n Carriage return \r Horizontal tab \t Vertical tab \v Backslash \\\ Question mark \? Single quote \\' Double quote \\"
02. The `scanf` Function
`printf`가 specified format으로 출력하는것과 같이 `scanf`는 각각의 형식에 따라 입력을 처리한다. `scanf`의 format string은 `printf`처럼 기본 문자들과 conversion specifications이 들어있다. 기본적으로 `scanf`에게 할당된 conversions는 `printf`와 기본적으로 같다.
`scanf`는 강력하지만 용서할 수 없는 데이터 처리 방식이다. 많은 C 프로그래머들은 `scanf` 사용을 피하고 문장 전체를 읽어와 나중에 숫자 형태로 변환한다. 왜냐하면 `scanf`는 예상치 못한 사용자의 입력을 처리하기 힘들기 때문인데, 사용하기 간단하기 때문에 C를 시작할 때 사용하는 방법이다.
How `scanf` Works
`scanf`는 내가 인지하지 못하는 많은 것들을 한다. `scanf`는 정확히는 입력된 문자들과 conversion specifications을 비교하는 "pattern-matching" 함수다. 함수가 `call`되면 문자열을 왼쪽부터 분석하기 시작한다. 빈칸을 필요하면 넘기면서 `scanf`는 format string의 conversion specification에 해당하는 문자를 찾는다.
기본적으로 `printf`처럼 `scanf`도 format string을 가지고 동작한다. `scanf`가 호출되면 우선 문자열에 대한 정보를 왼쪽부터 파악하고 빈칸을 필요하면 무시하면서 각각 conversion specification에 해당하는 적절한 형식의 입력값을 찾는다. 만약 conversion specification과 맞지 않는 문자가 들어오면 그 즉시 종료하고 성공적으로 입력받았다면 다음 항목으로 넘어간다.
`scanf`는 숫자의 시작을 찾을 때 공백문자(the space, horizontal and vertical tab, form-feed, and new-line characters)를 무시한다. 다음 경우를 고려해보자.
scanf("%d%d%f%f", &i, &j, &x, &y);
유저가 다음 세 줄을 입력했다고 가정해보자.
1 -20 .3 -4.0e3
`scanf`는 다음같이 문자들이 연속된 stream을 입력받는다.
●●1◘-20●●●.3◘●●●-4.0e3◘ (◘ 은 개행문자, ● 은 공백문자)
`scanf`는 숫자를 입력받을 때 각 숫자 앞의 공백문자를 무시하므로 성공적인 입력을 받을 수 있다. 다음 `s`는 skip된 문자를 나타내고 `r`은 read된 문자를 나타낸다.
●●1◘-20●●●.3◘●●●-4.0e3◘
`s` `s` `r` `s` `r` `r` `r` `s` `s` `s` `r` `r` `s` `s` `s` `s` `r` `r` `r` `r` `r` `r` `scanf`는 마지막 개행문자를 제외하고 읽는다. 이 개행문자는 다음 `scanf` 호출에서 읽게된다.
`scanf`가 정수, 실수를 인지하는 규칙은 무엇인가. 정수를 읽는다고 요청하면 `scanf`는 먼저 10진수 또는 부호를 입력받는다. 그 후 10진수가 아닐때까지 입력을 받는다. 실수를 읽는다고 요청하면 `scanf`는 다음 순서에 따라 입력을 받는다.
-
첫 문자가 부호라면 부호를 입력받고
-
10진수가 아닐 때까지 10진수를 입력받고
-
소숫점 또는 `e`(or `E`)가 있다면 입력받고 10진수가 있는대로 입력받는다.
`%e`, `%f`, `%g`는 소숫점까지 입력받는다는 점이 같아서 서로 바꿔쓸 수 있다.
Ordinary Characters in Format Strings
"pattern-matching" 개념을 이용하면 conversion specifications를 갖고있는 format strings을 작성할 수 있다. 일반 문자가 `scanf`에 넘어오면 그 문자가 공백문자인지 아닌지에 따라 처리방식이 달라진다.
-
***White-space characters.***
-
format string에 한개 또는 여러개의 공백문자를 받으면 공백문자가 아닐 때까지 입력을 받는다 (이 과정을 "put back"이라고 부른다). 따라서 한 개의 공백문자는 입력에서 여러개의 공백 문자에 대응된다.
-
또한 공백문자를 format string에 넣는 것이 입력에 공백을 강조하는 것도 아니다.
-
format string의 공백문자는 입력의 한개 또는 여러개의 공백문자에 대응하기도하고 또는 아무것도 대응되지 않을 수 있다.
-
-
***Other characters.***
-
format string에 공백 문자가 아닌 다른 문자를 입력받는다면 `scanf`는 입력된 문자와 비교한다.
-
만약 두 문자가 같다면, `scanf`는 그 문자를 버리고 다음 format string을 처리하기 시작한다.
-
만약 두 문자가 같지 않다면, 일치하지 않는 입력 문자를 포함한 뒷 문자열을 모두 입력에 돌려놓고 더이상의 format string을 처리하거나 입력을 읽는 등의 처리를 하지 않고 바로 종료한다.
-
입력에 돌아간 문자열은 다음 입력함수(`scanf` 등)가 처리한다.
-
Confusing `printf` with `scanf`
`scanf`와 `printf`의 호출에서 둘은 비슷해 보이지만 상당히 큰 차이가 있고, 이 차이를 인지하지 못하는건 프로그램 건강에 상당히 안좋다.
가장 흔한 실수중 하나는 `printf`의 호출 중 변수 앞에 `&`를 붙이는 것이다.
printf("%d %d\n", &i, &j); /* WRONG */
이런 실수는 다행이지만 찾아내기 쉽다. `printf`가 `i`와 `j`의 값 대신 이상한 값을 출력하기 때문이다.
`scanf`가 입력받을 문자 앞의 공백문자를 모두 무시하기 때문에 사실 format string에 conversion specifications을 구분하기 위한 공백 문자는 필요하지 않다. `printf`와 `scanf`의 format string이 비슷하기 때문에 `scanf`가 예상치 못한 이상한 행동을 하기도 한다. 다음 구문을 고려해보자.
scanf("%d, %d", &i, &j);
`scanf`는 먼저 정수 입력값을 찾고 `i`에 저장한다. `scanf`는 그 다음 입력값으로 쉼표를 찾고 다음 입려값이 쉼표가 아닌 공백이라면 `scanf`는 `j`를 읽지 않고 종료한다.
`printf`의 format string이 보통 `\n`로 끝나는 것 처럼 `scanf`의 format string을 `\n`으로 끝내는 생각은 좋지 않은 생각이다. `scanf`한테 `\n`은 공백문자와 같다. 따라서 `scanf`의 format string을 `\n`으로 끝낸다면 끝없이 공백문자를 입력받고 공백이 아닌 문자를 받을 때 종료한다. 이러한 format string은 사용자가 공백이 아닌 문자를 입력받을 때 까지 프로그램을 "hang"상태로 만들 수 있어서 주의해야 한다.
'프로그래밍 언어 > C' 카테고리의 다른 글
06. Loops (0) 2020.03.30 05. Selection Statements (0) 2020.03.21 02. C Fundamentals (0) 2020.03.12 01. Introducing C (0) 2020.03.11 기초의 중요성 (0) 2020.03.05 -