36-1-다.입력 스트림

C++의 표준 입력 스트림은 cin 객체이다. cin 객체로 입력을 받아 >> 연산자(Extraction)로 대상 변수로 보내기만 하면 된다. >> 연산자가 대부분의 기본 타입에 대해 오버로딩되어 있기 때문에 타입에 상관없이 입력을 받을 수 있다. cout과 마찬가지로 >> 연산자가 cin 객체의 레퍼런스를 리턴하므로 연쇄적으로 입력을 받을 수도 있다.

입력 방법은 상당히 직관적이므로 사용하기 쉽지만 입력이란 출력과는 달리 에러가 발생할 소지가 많기 때문에 훨씬 더 까다롭다. 다음 예제는 cin 객체와 >> 연산자를 사용하여 정수값과 문자열을 입력받는다. 간단한 입력 동작을 테스트하므로 별도의 프롬프트는 출력하지 않았다.

 

: cin

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i;

     char str[128];

 

     cin >> i;

     cout << i << endl;

     cin >> str;

     cout << str << endl;

}

 

예제를 실행하면 커서가 깜박거리는데 순서대로 정수값과 문자열을 입력하면 된다. 입력된 값을 cout으로 그대로 화면으로 다시 출력해 보았다. 실행 결과는 다음과 같다.

 

1234

1234

abcdefghijkl

abcdefghijkl

 

cin 입력 객체와 >> 연산자는 다음과 같은 중요한 네가지 특징을 가지고 있다.

 

① 공백은 건너 뛰고 입력받는다. 공백은 실제 입력받고자 하는 대상이 아니며 데이터의 구분을 위한 구분자로 삽입되는 경우가 많기 때문에 입력값의 일부라고 볼 수 없다. 그래서 앞쪽의 공백은 일단 건너뛰고 유효한 문자부터 읽기 시작한다. 여기서 공백이란 스페이스 뿐만 아니라 탭과 개행 코드도 포함된다. 어떻게 보면 합리적이지만 공백 자체를 입력받고자 할 때는 다른 방법을 사용해야 한다.

② 읽을 수 있는 유효한 입력까지만 읽으며 무효한 문자를 만나는 즉시 읽기를 중지한다. 예를 들어 정수값을 읽는다면 정수를 구성하는 아라비아 숫자와 +, - 부호 기호 등만 유효한 입력으로 간주하며 숫자가 아닌 입력이 들어오면 즉시 읽기를 중지한다. 문자열의 경우도 공백이 입력되면 공백 이전까지만 읽는다. 즉 단어만 읽을 수 있다.

③ 앞 규칙에 의해 읽지 못한 데이터는 버퍼에 그대로 남겨지며 다음번 읽을 때 버퍼에 남아 있는 값이 읽혀진다. 만약 버퍼에 저장된 값이 쓸모없는 값이라면 버퍼를 비운 후 다시 입력을 받아야 하는 번거로움이 있다.

④ 문자열의 경우 문자형 배열에 저장되는데 C에서와 마찬가지로 배열의 끝 점검은 하지 못한다. 그래서 입력받을 데이터를 저장할만한 충분한 공간을 제공해야 한다. 위 예제에서 str 버퍼를 5 정도의 크기로 축소하고 4문자 이상을 입력하면 다운될 수도 있다. 아무리 C++이라 하더라도 전달받는 정보가 배열의 시작 번지밖에 없으므로 배열 크기 점검을 할 수 없다.

 

위 예제를 실행해 놓고 "  123abc"를 입력해 보면 cin과 >> 연산자의 모든 특성을 한꺼번에 테스트해 볼 수 있다. 123앞쪽에 일부러 공백 두 개를 입력해 보았다.

 

  123abc

123

abc

 

"  123abc"까지 입력하고 Enter를 누르는 순간 123과 abc가 화면으로 출력되면서 프로그램이 종료될 것이다.

cin >> i 입력문이 앞쪽의 공백은 일단 건너 뛰고 1부터 읽기 시작한다. 그리고 2와 3을 읽고 a를 만날 때 읽기를 중지하여 123이라는 값을 i에 대입하고 리턴한다. 이때 나머지 뒷부분의 abc는 버퍼에 그대로 남아 있으며 다음 번 입력을 기다린다. cin >> str 입력문은 버퍼에 남아 있는 데이터가 있으므로 이 데이터를 별도의 대기없이 그대로 읽어올 것이다. 이런 입력 방법이 대개의 경우에는 문제가 없지만 문자나 문자열을 읽을 때는 원하는 목적에 맞지 않을 수도 있다. 다음 예제는 문자 하나와 문자열을 읽는다.

 

: cinstring

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     char ch;

     char str[128];

 

     cin >> ch;

     cout << ch << endl;

     cin >> str;

     cout << str << endl;

}

 

ch는 임의의 문자 하나를 입력받을 수 있지만 어떻게 하더라도 공백은 입력받지 못한다. >> 연산자가 공백을 무조건 건너 뛰어 버리기 때문이다. str은 공백으로 구분된 단어를 입력받지만 공백이 포함된 문장을 입력받지는 못한다. 사람 이름이나 제품명 따위의 짧은 문자열 정도만 입력할 수 있으며 주소나 메일의 제목, 일기 따위의 복잡한 문장은 입력할 수 없다. 그래서 cin 객체는 이런 문제를 해결하기 위해 >> 연산자와는 별도로 단일 문자 입력과 문장 입력 함수를 따로 제공한다.

 

int get(void);

basic_istream& get(char& c);

basic_istream& getline(char *s, streamsize n, char delim='\n');

 

get 멤버 함수는 두 가지 형식으로 사용되는데 입력받은 문자를 리턴값으로 돌려 주는 형식과 인수로 전달된 char형의 레퍼런스에 입력된 값을 대입하는 형식이 있다. 첫 번째 get 함수는 C의 getch 함수와 유사해서 사용하기 편리하다는 장점이 있고 두 번째 get 함수는 입력 객체의 레퍼런스를 리턴하므로 연쇄적인 입력이 가능하다는 장점이 있다.

 

cin.get(ch).get(ch2) >> i;

 

두 형식 모두 입력된 문자 하나를 무조건 돌려주도록 되어 있으므로 공백이나 개행 코드도 입력받을 수 있다. getline 함수는 문자형 배열에 문자열을 입력받되 배열의 크기를 전달받아 배열 경계를 넘어서까지 입력받지 않으므로 안전하다. 세 번째 인수 delim은 이 문자가 입력되기 전까지 계속 입력받도록 하는데 디폴트값은 개행 코드로 되어 있으므로 개행 코드 전의 문자열을 모두 입력받는다. 따라서 >> 연산자보다는 훨씬 더 안정적이며 공백을 포함한 문자열도 입력받을 수 있다. 다음과 같이 예제를 수정해 보자.

 

: cinstring2

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     char ch;

     char str[128];

 

     cin.get(ch);

     cout << ch << endl;

     cin.get(ch);

 

     cin.getline(str,128);

     cout << str << endl;

}

 

공백이나 개행 코드, 탭 문자도 무조건 입력받으며 127문자 길이 내에서 공백을 포함한 긴 문장도 입력받을 수 있다. 공백 무시 여부와 문자열 내의 공백 포함 여부에 따라 >> 연산자나 get, getline 함수 중 적당한 함수를 사용하면 된다. 참고로 getline 함수와 똑같은 원형을 가지는 get 함수도 중복 정의되어 있다.

입력은 출력과는 달리 동작중에 에러 발생 가능성이 높다. 그래서 원하는 값이 제대로 입력되었는지 항상 점검해야 하는데 에러를 검출할 수 있는 여러 가지 장치가 준비되어 있다. 우선 >> 연산자의 리턴값을 에러 처리에 곧바로 사용할 수 있는데 입력문 자체를 if문이나 while문의 조건식으로 사용하는 것이다.

 

: cinerror

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i;

 

     if (cin >> i) {

          cout << i << endl;

     } else {

          cout << "실패" << endl;

     }

}

 

정수형 값 하나를 입력받아 그대로 출력하되 에러가 발생했을 경우 실패했다는 것을 문자열로 출력하도록 했다. 123같은 정수를 입력하면 정상적으로 실행되겠지만 abc같은 문자열을 입력하면 에러 상황이 될 것이다. >> 연산자는 입력 객체의 레퍼런스를 리턴하는데 실패할 경우 입력 객체의 에러 비트를 설정하는 방식으로 연산식 전체의 평가 결과를 false로 만들므로 입력문 자체를 if문 안에 넣어 에러 발생 여부를 조사할 수 있다.

cin 객체는 내부적으로 에러 발생 여부를 표시하는 세 개의 플래그를 유지하는데 입력 동작 후 이 플래그들의 값을 점검하여 어떤 에러가 발생했는지를 알 수 있다. 에러 플래그는 다음 세 종류가 정의되어 있는데 콘솔 입력뿐만 아니라 파일 입력까지 고려하여 일반적인 에러를 모두 처리할 수 있도록 정의되어 있다.

 

플래그

설명

failbit

입력에 실패했다는 뜻이다. 정수를 입력받는데 문자가 입력된 경우 1 된다.

eofbit

파일 끝이라는 뜻이다. 이상 읽을 문자가 없으므로 에러를 리턴한다.

badbit

스트림이 물리적으로 손상되어 읽을 없다.

goodbit

상기 에러가 발생하지 않았다는 뜻이며 0으로 정의되어 있다.

 

각각의 에러가 발생했는지를 조사하는 fail(), bad(), eof(), good() 멤버 함수가 정의되어 있으므로 이 함수들을 호출하여 입력 객체의 상태가 어떠한지를 조사할 수 있다. 위 예제를 에러 플래그를 조사하는 방식으로 바꾸어 보면 다음과 같다.

 

: cinerrorbit

#include <Turboc.h>

#include <iostream>

using namespace std;

 

void main()

{

     int i;

 

     cin >> i;

     if (cin.good()) {

          cout << i << endl;

     } else {

          cout << "실패" << endl;

     }

}

 

cin >> i; 문장으로 입력을 받은 후 good() 함수를 호출해 보면 제대로 정수값을 입력받았는지를 알 수 있다. if (!cin.fail()) 로 점검해도 결과는 거의 동일하다. cin 객체는 일단 에러가 발생하면 계속 에러 상태를 유지하며 이 상태에서는 어떠한 입력도 받아들이지 못한다. 그래서 별도의 함수로 에러 상태를 리셋해야 하는데 이때는 다음 멤버 함수들을 사용한다.

 

iostate rdstate() const;

void setstate(iostate state);

void clear(iostate state = goodbit);

 

rdstate는 에러 플래그의 값을 리턴하며 setstate는 에러 플래그를 설정하거나 해제한다. clear도 에러 플래그를 변경하는데 지정한 플래그값만 남기고 나머지를 모두 리셋한다는 점이 setstate와는 다르다. 즉, setstate는 지정한 플래그만 설정하지만 clear는 나머지 플래그까지 통째로 리셋한다.

 

cin.setstate(failbit); // failbit를 설정한다. 나머지 비트는 원래 상태를 유지한다.

cin.clear(failbit);     // failbit를 설정하고 나머지 비트는 리셋한다.

 

이 외에 잘 사용되지는 않지만 다음과 같은 멤버 함수들이 더 정의되어 있다.

 

멤버 함수

설명

ignore

지정한 길이만큼 또는 지정한 문자가 나올 때까지 데이터를 무시한다. 버퍼에 들어 있는 데이터를 읽어서 버리고자 함수를 쓴다.

peek

버퍼에 있는 데이터를 읽기만 하고 제거하지는 않는다. 어떤 데이터가 버퍼에 있는지 살짝 들여다 보기만 함수를 사용한다.

gcount

입력문에 의해 실제로 읽혀진 데이터의 길이를 조사한다. >> 연산자로 읽은 길이는 조사할 없으며 get, getline, read 등의 함수로 읽은 길이만 조사할 있다.

putback

특정 데이터를 버퍼에 다시 밀어 넣는다. 마치 어떤 문자가 입력된 것처럼 만들고 싶을 함수를 사용하는데 함수가 밀어 넣은 데이터는 다음 입력 함수에 의해 꺼내질 것이다.

 

그 외의 고급 함수들은 주로 파일 입출력 스트림에 대해 사용된다.