백준/이걸 몰랐네

cin과 scanf는 공백 문자를 어떻게 인식하는가

발전생 2020. 8. 25. 23:57
백준 알고리즘 문제를 풀 때 고민을 많이 하게 된다.

기본 입출력부터 고민거리가 생긴다.

그래서 c++에서 사용하는 입력함수 cin과 scanf를 비교해봤다.

 

//편의상 delete문 제외


cin 테스트

#include <cstdio>
#include <iostream>

using namespace std;
int main() {
	int n;
	scanf("%d", &n);

	char** board = new char* [n];
	for (int i = 0; i < n; i++) {
		board[i] = new char[n];
	}

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> board[i][j];
		}
	}

	cout << "====배열 출력=====" << endl;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
        		cout << board[i][j];
		}
	}

	return 0;
}

char 타입의 문자를 담는 이차원 배열 board에 cin으로 입력을 받아봤다.

그랬더니 아래와 같이 출력되었다.

2
AB
CD
====배열 출력=====
ABCD

cin은 엔터 입력('\n')을 board 배열에 넣지 않았다.


왜 이런 결과가 나오는 걸까?

template<class CharT, class Traits >
basic_istream<CharT,Traits>& operator>>( basic_istream<CharT,Traits>& st, CharT& ch );

Behaves as an FormattedInputFunction. After constructing and checking the sentry object, which may skip leading whitespace, extracts a character and stores it to ch. If no character is available, sets failbit (in addition to eofbi that is set as required of a FormattedInputFunction
).

 

std::cin은 basic_istream에 존재하는 객체이다. 보면 앞에 있는 whitespace를 건너뛰고 문자 하나를 추출해서 >> 뒤에 오는 변수에 저장한다고 나와있다.

 

자 처음에 2->엔터를 입력 시 스트림으로 "2\n"이 들어온다.

'2'는 scanf 때 n에 저장하기 위해 버퍼에서 버려지고 "\n"이 남는다.

 

반복문에서 cin의 >> 호출이 일어나고 버퍼에 '\n'(whitespace)밖에 없다. 하지만 whitespace는 건너뛰니 새로운 문자를 받는다.

여기에서는 "AB\n"을 입력했으니 A, B를 추출해서 board에 저장한다.

마찬가지로 whitespace만 남았으니 건너뛰고 새로운 문자를 받는다.

"CD\n"을 입력받고나서 C, D를 추출해서 board에 저장한다.

배열이 모두 찼으니 이제 출력이 이루어진다.


scanf 테스트

#include <cstdio>
#include <iostream>

using namespace std;
int main() {
	int n;
	scanf("%d", &n);

	char** board = new char* [n];
	for (int i = 0; i < n; i++) {
		board[i] = new char[n];
	}

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			scanf("%c", board[i] + j);
		}
	}

	cout << "====배열 출력=====" << endl;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			printf("%c", board[i][j]);
		}
	}

	return 0;
}

이번에는 scanf로 char 타입의 문자를 담는 이차원 배열 board에 입력을 받아봤다.

그랬더니 아래와 같이 출력되었다.

2
AB
====배열 출력=====

AB

cin과 달리 scanf는 엔터 입력에 대해서 민감하다.

2를 입력한 뒤 친 엔터, AB를 입력한 뒤 친 엔터 모두 scanf로 받아버렸다.

이 결과는 cout으로 출력을 해보아도 마찬가지다. 이미 엔터를 받아서 배열에 저장해버린 거다.

 

n*n 배열을 만들어서 각각에 알파벳 문자를 넣고자 하였으므로 %c를 이용한 scanf 방법은 적합하지 않다.

물론 getChar()를 이용해 버퍼의 \n문자를 빼줄 수도 있지만 머리만 복잡해질 뿐이다.


이렇게 되는 이유를 보자면 이렇다.

 

scanf는 stdin에서 데이터를 읽는다. stdin은 표준 입력 스트림이다. 키보드의 입력이 이 곳으로 들어온다.

reads the data from stdin

 

%[, %c, %n을 제외하고는 선행하는 공백문자들을 버린다고 나와있다. 

즉, %c는 공백문자를 모두 다 취급한다.

 

처음 입력한 건  2->엔터였고 버퍼에는 "2\n"가 들어가게 된 것이다.

맨 앞의 scanf(n에 입력 받는) 함수 호출 때 버퍼에서 2를 내줬고

반복문 속 첫번째 scanf에서 버퍼에 있던 '\n'를 받아버린 것이다.

그 뒤에는 버퍼에 남은 게 없으니 scanf에 의해 추가적으로 입력을 받게 되고 버퍼에 'A' 'B' '\n'를 넘겨준다.

반복문의 남은 세번에서 board에는 각각 버퍼의  'A' 'B' '\n'를 받게 된다.

그러니 출력 결과가 "한 줄 띄고 AB 한 줄 띄고"가 되고 입력도 한 줄만 받게 된다.


 

정리

 

cin으로 문자를 받을 시: 

모든 whitespace(' ', '\n', '\t' 등)는 무시하고 저장한다.

 

scanf(%c)로 문자를 받을 시:

버퍼에 있는 whitespace가 다음 문자에 저장된다.


인용

 

https://en.cppreference.com/w/