[2-7] 표준 입력 : scanf(), getchar(), getc(), gets()

표준 입력은 유닉스 및 유닉스 계열 운영체제에서 프로그램과 환경 사이에 미리 연결된 입력 통로입니다. PC에서 표준 입력은 일반적으로 키보드입니다. 임베디드 단말에서는 키패드 등이 될 수도 있죠.

[2.5] 표준 출력, printf(), putchar(), putc()’에서 표준 출력인 콘솔로 문자를 출력했으니 표준 입력을 받아봅시다.

문자열, 정수, 문자 등을 입력받을 수 있습니다. 여기서는 주로 사용하는 ​scanf, getchar(), fget() 함수만 살펴봅니다.

scanf() 함수

scanf()는 표준 입력에서 입력한 형식화된 바이트 입력을 읽습니다. 함수 원형은 다음과 같습니다.

int scanf( const char *restrict format, ... );
  • 입력 : 형식지정자를 사용해 입력받습니다.
  • 반환값 int : 성공하면 성공적으로 할당된 인수 수, 실패하면 EOF를 반환합니다.

printf()와 같이 가변변수를 사용합니다. 사용법은 다음과 같습니다.

#include <stdio.h>

int main(void) {
	char ch = 0;
	
	scanf("%c", &ch);
	printf("input is %c\n", ch);
	
	return 0;
}

로컬 PC에서 이 프로그램을 실행하면 명령 프롬프트가 뜰 겁니다. 숫자 하나면 넣으면 됩니다. https://ideone.com에서는 실행하기 전에 다음과 같이 입력창버튼 에 미리 값을 입력하면 됩니다.

입력창

[출력]

press any key : 3
c is 3

입력값으로 3을 넣었더니 위와 같이 출력됩니다. printf()와 마찬가지로 형식지정자로 s(문자열), i(정수형), f(실수형) 등을 받을 수 있습니다.

초보자가 scanf()를 쓰면서 가장 많이 실수하는 곳이 있습니다. 바로 scanf(“%c”, &ch);에서 &ch 부분입니다. &를 빼먹게 되어 버그를 잡는데 잠을 설치게 됩니다. 표준 입력으로 받은 값을 해당 변수의 주소로 보내주기 때문입니다. 반드시 &를 빼먹지 마세요!

이번에는 문자열을 받아보겠습니다.

#include <stdio.h>

int main(void) {
	char str[5];
	
	printf("press one word : ");
	scanf("%s", str);
	printf("\ninput : %s\n", str);
	
	return 0;
}

[출력]

press one word : hello world
input : hello

“hello world”를 입력했지만 str 크기가 5라서 딱 문자 5개까지만 입력으로 받았습니다.

다음과 같이 한 번에 여러 변수를 입력 받을 수 있습니다.

#include <stdio.h>

int main(void) {
	char c;
	char str[10];
	int i;
	
	scanf("%c %s %d\n", &c, str, &i);
	
	printf("your input : %c, %s, %d\n",c, str, i);
	return 0;
}

순서대로, 문자, 문자열, int형 변수값이 입력되었습니다. 문자열을 입력받는 str[ ] 변수 앞에는 앞에는 &가 붙지 않았습니다. 배열의 이름은 배열의 시작 주소를 나타내기 때문입니다. 이미 주소인데 &를 붙일 필요가 없는거죠.

이 함수는 쓰인세도 다양하고 초보자가 실수하기도 쉽습니다. 예를 들어 다음 두 줄의 코드는 서로 다른 형식의 입력을 받습니다.

scanf("%c %d\n", &c, &i);
scanf("%c, %d\n", &c, &i);

첫 번째 코드의 입력 형식은 다음과 같습니다.

문자 + 공백 + 숫자

그런데 두 번째 코드의 입력 형식은 다음과 같습니다.

문자 + 쉼표 + 공백 + 쉼표 + 숫자

이를 활용하면 형식 고정적인 데이터를 쉽게 불러올 수 있습니다.

한번 코드를 작성해 실험해보시기 바랍니다.

이상 관우였습니다!

제갈량의 동남풍 : 왜 scanf() 함수는 주소로 인수를 입력해야 하나?

오래간만에 인사드리는 제갈량입니다. scanf( ) 함수를 사용하면서 ‘scanf( ) 함수는 왜 주소를 인수로 전달해야 하는 걸까? 그냥 & 없이 변수명만 넣어주면 알아서 값을 복사해주면 안 되나’라는 의문이 들지 않나요?

함수에서 반환값의 위치를 떠올려보세요.

int scanf( const char *restrict format, ... );

int는 반환값이고, ‘const char *restrict format, …’는 scanf() 함수 입장에서 파라미터, 즉 입력값입니다.

코드가 어떻게 동작하는지 아래 그림을 참조하면서 살펴보겠습니다.

입력흐름도

입력값은 scanf로 전달되는 값이므로, 주소를 전달하지 않고서야 이 파라미터에 무언가를 쓸 방법이 없습니다. 그런데 ① 주소를 전달한다는 사실만 안다면 해당 주소에 ② 표준입력된 값을 써두면 되니까 원활히 표준 입력값을 전달 할 수 있는 겁니다. 그래서 인수로 주어지는 변수의 주소를 입력하고, 해당 주소에 값을 쓰는 거죠.

getchar() 함수

getchar() 함수는 표준 입력에서 1바이트를 읽습니다. 함수원형은 다음과 같습니다.

int getchar(void);
  • 입력 : 없습니다.
  • 반환 : 성공하면 입력받은 문자를 반환합니다. 실패하면 EOF를 반환하고 표준 에러에 오류 메시지를 보냅니다.

예제를 살펴보겠습니다.

#include <stdio.h>

int main(void) {
	char ch;
	
	printf("press one word : "); 
	
	// stdin에서 읽기 
	ch = getchar ( );
	printf("\ninput : %c\n", ch);
	
	return 0;
}

[출력]

press one word : g
input : g

g를 표준 입력에서 입력받아 ch에 저장하고 printf()문으로 출력했습니다.

fget()

사용자가 지정한 파일 스트림에서 1바이트를 읽는 함수입니다. 함수 원형은 다음과 같습니다.

int gutc(FILE *stream);
  • 입력 : 1바이트를 읽을 파일 스트림을 지정합니다.
  • FILE *stream : 파일이거나 장치일 수 있습니다. stdin은 표준 입력, stdout은 표준 출력, stderr는 표준 에러 장치를 지칭합니다. 물론 그밖에 다른 장치와 파일 기술자도 사용할 수 있습니다.
  • 반환 : 성공하면 성공한 글자 수를, 실패하면 EOF를 반환하고, 표준 에러에 오류 메시지를 남깁니다.

파일 기술자(file descriptor)를 파일 설명자, 파일 디스크립터라고도 합니다.

FILE *stream에 표준 장치인 stdout를 인수로 입력하면 gutchar()는 완전히 같은 기능을 수행합니다. 앞서 다룬 gutchar() 예제를 gutc()로 구현해보겠습니다.

#include <stdio.h>

int main(void) {
	char ch;
	
	printf("press one word : "); 
	
	// stdin에서 읽기 
	ch = getc(stdin);
	printf("\ninput is %c\n", ch);
	
	return 0;
}

[출력]

press one word : g
input is g

함수 이름을 바꾸고 stdin만 인수로 주었습니다. 예제 분석에 무리가 없을 겁니다. 그런데 이 함수는 기억해 둘 필요가 있습니다. 표준 입력 말고 파일이나 다른 장치를 읽을 때 유용합니다. 보통 다음과 같이 사용합니다.

    while  ( ( ch = getc(파일 스트림 ) )  ! =  EOF )  

위 while문은 읽은 문자가 EOF일 때까지 읽습니다. EOF는 파일의 끝이라고 표준 출력에서 이야기를 했었습니다. 왜 파일이나 장치를 읽을 때 getc() 함수가 유용한지 이제 감이 오죠?

gets() 함수

gets() 함수는 표준입력에서 개행 문자(\n)나 파일 끝을 나타내는 EOF를 만나기까지 문자열을 가져옵니다.

함수 원형은 다음과 같습니다.

char * gets ( char * str );
  • char * str : 문자열을 읽을 메모리 공간입니다.
  • 반환 : 아무것도 읽지 못하거나 오류가 발생하면 NULL을, 읽었다면 읽은 문자열을 반환합니다.

사용 예는 다음과 같습니다.

#include <stdio.h>
#include <string.h>

int main(void) {
	char str[20];
	
	gets(str);
	printf("input : %s\n", str);
	return 0;
}

hello world를 키보드로 입력한 결과 출력은 다음과 같습니다.

[결과]

hello world

문자열이 얼마나 큰지 모른다면 충분한 메모리 공간을 확보해 사용하고, 에러와 예외 체크도 해야 합니다.