[C 03] 서식 입출력(printf·scanf)

03.1 값을 화면에 찍는 printf

화면에 무언가를 보여 주려면 표준 입출력 라이브러리의 printf 함수를 씁니다. 파일 맨 위에 #include <stdio.h>를 적어 이 함수를 불러오고, 큰따옴표 안의 글자는 적은 그대로 나옵니다. 핵심은 서식 지정자(format specifier, % 로 시작하는 작은 자리표시자)입니다. printf는 문자열을 훑다가 %를 만나면 뒤에 딸려 온 값을 그 자리에 끼워 넣습니다.


#include <stdio.h>

int main(void) {
int apples = 5;
printf("사과 %d개\n", apples);
return 0;
}


출력:
사과 5개


%d 자리에 apples의 값 5가 들어갔습니다. 맨 끝의 \n은 줄바꿈을 뜻하는 특수 문자라, 이걸 빼면 다음 출력이 같은 줄에 이어 붙습니다.

03.2 자료형마다 다른 서식 지정자

서식 지정자는 넣을 값의 자료형에 맞춰 골라야 합니다. 정수는 %d, 실수는 %f, 문자 하나는 %c, 문자열은 %s입니다. 함정. 값과 지정자의 종류가 어긋나면(예를 들어 실수를 %d로 찍으면) 컴파일러가 막아 주지 않고 엉뚱한 값이 나오거나 정의되지 않은 동작(undefined behavior, 표준이 결과를 정하지 않아 무슨 일이든 일어날 수 있는 상태)이 됩니다.


int n = 42;
double pi = 3.14;
char grade = 'A';
char name[] = "홍길동";
printf("정수 %d\n", n);
printf("실수 %f\n", pi);
printf("문자 %c\n", grade);
printf("문자열 %s\n", name);


출력:
정수 42
실수 3.140000
문자 A
문자열 홍길동


%f는 기본으로 소수점 아래 여섯 자리까지 찍습니다. 문자 하나는 작은따옴표 'A', 문자열은 큰따옴표 "홍길동"으로 적는 차이도 함께 익혀 둡니다.

03.3 소수 자릿수와 자리 맞춤

%와 지정자 사이에 숫자를 끼우면 출력 모양을 다듬습니다. %.1f는 소수점 아래 한 자리, %.2f는 두 자리로 반올림해 찍고, %5d는 최소 다섯 칸을 확보해 오른쪽으로 붙여 정렬합니다. 여러 줄의 숫자를 표처럼 맞출 때 씁니다.


double height = 175.567;
printf("%.1f\n", height); // 175.6
printf("%.2f\n", height); // 175.57
printf("%5d\n", 7);
printf("%5d\n", 100);


출력:
175.6
175.57
7
100


7 앞에 공백 네 칸, 100 앞에 두 칸이 붙어 오른쪽 끝이 가지런히 맞습니다.

03.4 퍼센트 기호 자체를 찍기

%는 서식 지정자의 시작 신호라, 글자 그대로의 % 하나를 찍고 싶으면 %%처럼 두 번 적습니다.


int rate = 30;
printf("할인율 %d%%\n", rate);


출력:
할인율 30%


주의. "50%"라고 무심코 한 번만 적으면, 뒤따르는 글자를 지정자로 오해해 화면이 깨지거나 UB가 됩니다.

03.5 키보드로 값을 받는 scanf

printf가 내보내기라면 scanf는 받아들이기입니다. 사용자가 키보드로 친 값을 변수에 담는데, 여기서 초보자가 가장 많이 걸리는 규칙이 있습니다. 값을 저장할 변수 앞에 & (주소 연산자)를 붙여야 한다는 점입니다. &age는 "age가 실제로 놓인 메모리 주소"라는 뜻이고, scanf는 그 주소를 받아 그 자리에 입력값을 써 넣습니다.


int age;
printf("나이를 입력하세요: ");
scanf("%d", &age);
printf("입력한 나이: %d\n", age);


출력(사용자가 20 입력):
나이를 입력하세요: 20
입력한 나이: 20


함정. &를 빠뜨린 scanf("%d", age);는 age에 든 값을 주소로 착각해 엉뚱한 메모리에 쓰려 들어 프로그램이 죽습니다(UB). 정수 입력은 printf와 똑같이 %d를 씁니다.

03.6 실수를 받을 때의 %lf

여기서 printf와 scanf의 지정자가 갈립니다. double(실수) 값을 출력할 때는 %f지만, scanf로 입력받을 때는 %lf(엘 에프)를 써야 합니다.


double weight;
printf("몸무게(kg): ");
scanf("%lf", &weight);
printf("몸무게는 %.1fkg입니다.\n", weight);


출력(사용자가 62.5 입력):
몸무게(kg): 62.5
몸무게는 62.5kg입니다.


함정. 입력을 scanf("%f", &weight)로 받으면 double 자리에 float 크기만큼만 써 넣어 값이 깨집니다. 외우는 법은 이렇습니다. 실수 입력은 %lf, 실수 출력은 %f. float 자료형을 쓸 때만 입력도 %f입니다.

03.7 문자 하나를 받을 때의 앞 공백

문자 하나는 %c로 받습니다. 그런데 앞서 숫자를 입력받은 뒤 이어서 %c로 문자를 받으면, 사용자가 엔터를 칠 때 남긴 개행 문자(\n)가 그대로 문자 자리에 딸려 들어가는 함정이 있습니다.


char answer;
printf("계속할까요? (y/n): ");
scanf(" %c", &answer); // %c 앞의 공백에 주목
printf("입력: %c\n", answer);


출력(사용자가 y 입력):
계속할까요? (y/n): y
입력: y


주의. "%c"가 아니라 " %c"처럼 앞에 공백 한 칸을 둡니다. scanf의 서식 문자열 속 공백은 "먼저 남아 있는 공백과 개행을 모두 건너뛰라"는 지시라, 앞 입력이 남긴 엔터를 깔끔히 무시합니다. 이 한 칸을 몰라 한참 헤매는 사람이 많습니다.

03.8 여러 값을 한 번에 받기

scanf의 서식 문자열에 지정자를 여러 개 늘어놓으면 값을 한꺼번에 받습니다. 변수 주소도 순서대로 &를 붙여 넘깁니다.


int w, h;
printf("가로 세로 입력(예: 3 4): ");
scanf("%d %d", &w, &h);
printf("넓이: %d\n", w * h);


출력(사용자가 3 4 입력):
가로 세로 입력(예: 3 4): 3 4
넓이: 12


사용자는 두 수를 공백이나 엔터로 나눠 칩니다. scanf는 %d를 만나면 알아서 앞쪽 공백을 건너뛰고 숫자만 읽으므로, 3 4를 한 줄에 치든 줄을 나눠 치든 똑같이 받아들입니다.

03.9 입력이 성공했는지 확인하기

scanf는 제대로 읽어 들인 값의 개수를 돌려줍니다. 이 반환값을 확인하면 사용자가 숫자 자리에 엉뚱한 글자를 치는 잘못된 입력을 걸러낼 수 있습니다.


int num;
printf("숫자 하나: ");
if (scanf("%d", &num) == 1) {
printf("받은 값: %d\n", num);
} else {
printf("숫자가 아닙니다.\n");
}


출력(사용자가 abc 입력):
숫자 하나: abc
숫자가 아닙니다.


주의. 값 하나를 잘 읽으면 1을 돌려줍니다. 반환값을 무시한 채 변수를 그냥 쓰면, 입력이 실패했을 때 변수에 남아 있던 쓰레기값을 읽어 예측 못 할 결과로 이어집니다. 서식 입출력은 이렇게 지정자 하나, 공백 한 칸, & 하나가 결과를 가르니, 손끝으로 직접 쳐 보며 출력을 눈으로 확인하는 것이 가장 빠른 길입니다.