C++ I / O in C++
1.C++ I / O library in c++
C++의 모든 입출력 클래스는 ios_base 를 부모 클래스로 하게 된다. ios_base 클래스는 많은 일은 하지 않고, 스트림의 입출력 형식 관련 데이터를 처리 한다. 예를 들어서 실수 형을 출력할 때 정밀도를 어떤 식으로 할 것인지에 대해, 혹은 정수형을 출력 시에 10진수로 할지 16진수로 할지 등을 이 클래스에서 처리 한다.
그 다음으로 ios 클래스가 있다. 이 클래스에서는 실제로 스트림 버퍼를 초기화 한다. 스트림 버퍼란, 데이터를 내보내거나 받아들이기 전에 임시로 저장하는 곳이라 볼 수 있다. 쉽게 설명하자면, 예를 들어서 하드디스크에서 파일을 하나 읽는다고 해보자. 만일 사용자가 1 바이트 씩 읽는 다고 했을 때, 실제로 프로그램은 1 byte 씩 읽는 것이 아니다.
실제로는 한 더미 (예를 들어서 512 바이트) 를 한꺼번에 읽어서 스트림 버퍼에 잠시 저장해 놓은 뒤에 사용자가 요청할 때 마다 1 바이트 씩 꺼내는 것이다. 만일 버퍼를 다 읽는다면 다시 하드에서 512 바이트를 읽게 되는 것이다. 이렇게 수행하는 이유는, 하드디스크에서 읽어오는 작업이 매우 느리기 때문에, 한 번 읽을 때 1 바이트 읽으면 엄청난 딜레이가 발생하게 된다. 이는 쓰는 작업에서도 마찬가지다. 쓸 때도 우리가 1 문자를 출력 하게 되면, 하드에 바로 쓰는 것이 아니라 일단 버퍼에 보관한 후, 어느 정도 모인 뒤에 출력하게 된다.
ios 클래스에선 그 외에도, 현재 입출력 작업의 상태를 처리 한다. 예를 들어, 파일을 읽다가 끝에 도달했는지 안했는지 확인하려면, eof() 함수를 호출하면 된다. 또, 현재 입출력 작업을 잘 수행할 수 있는지 확인하려면 good() 함수를 호출하면 된다.
2.C++ istream class
여태까지 ios_base 와 ios 클래스들이 입출력 작업을 위해 바탕을 깔아주는 클래스 였다면, istream 은 실제로 입력을 수행하는 클래스다. 대표적으로 항상 사용하던 operator>> 가 이 istream 클래스에 정의되어 있는 연산자다. 또, cin 은 istream 클래스의 객체 중 하나다. 그렇기 때문에 우리는
cin >> a;와 같은 작업을 할 수 있었던 것이다. 우리가, 어떤 타입에 대해서도 cin 을 사용할 수 있었던 이유는 (a 가 char 이냐 int 이냐에 상관없이) 바로 opeartor>> 가 그런 모든 기본 타입들에 대해서는 정의가 되어있기 때문이다.
그렇다고 해서, 우리가 언제나 위 타입들 빼고는 operator>> 로 받을 수 없는 것이 아니다. 실제로 istream 클래스의 멤버 함수로는 없지만
string s; cin >> s;
string 클래스의 객체 s 도 cin 으로 입력 받을 수 있다. 이유는 멤버 함수를 두는 것 말고도, 외부 함수로 연산자 오버로딩을 할 수 있기 때문이다.
istream& operator>>(istream& in, string& s) { // 구현 }
operator>> 의 또 다른 특징으로는, 모든 공백문자 (띄어쓰기나 엔터, 탭 등)을 입력시에 무시해버린다는 점이다.
만일 cin 을 통해서 문장을 입력 받는 다면, 첫 단어 만 입력 받고 나머지를 읽을 수 없다.
앞서 ios 클래스에서 스트림의 상태를 관리한다고 하였다. 이 때, 스트림의 상태를 관리하는 플래그 (flag - 그냥 비트 1 개라 생각) 는 4 개가 정의되어 있다. 이 4 개의 플래그들이 스트림이 현재 어떠한 상태인지에 대해서 정보를 보관한다는 뜻이다. 이 4 개의 플래그는 각각 goodbit, badbit, eofbit, failbit 이렇게 4 개 종류가 있다. 각각의 비트들이 켜져있는지, 꺼져있는지 (즉 1 인지 0 인지에 따라) 스트림의 상태를 알 수 있게 된다.
각각의 비트들에 대해 간단히 설명해보자면
goodbit : 스트림에 입출력 작업이 가능할 때
badbit : 스트림에 복구 불가능한 오류 발생시
failbit : 스트림에 복구 가능한 오류 발생시
eofbit : 입력 작업시에 EOF 도달시
위와 같은 상황 일 때 각각의 비트들이 켜지는 것이다. 만일 위와 같이 문자를 입력할 경우 operator>> 가 어떤 비트를 켜게 될까? 일단 eofbit 는 확실히 아니다. 끝에 도달한 것이 아니다. 그렇다면 badbit 일까? badbit 는 스트림 상에서 복구할 수 없는 문제시 켜지는데 위 경우는 그렇게 심각한 것은 아니다. 그냥 현재 스트림 버퍼에 들어가 있는 'c\n' 이 문자열을 제거해버리면 되기 때문이다. 위와 같이 타입에 맞지 않는 값을 넣어서 오류가 발생하는 경우에는 failbit 가 켜지게 된다. 그리고, 입력값을 받지 않고 리턴해버린다.
문제는 이렇게 그냥 리턴해버리면서 버퍼에 남아 있는 'c\n' 이 문자열에는 손대지 않는다는 것이다.
#include#include using namespace std; int main() { int t; while (cin >> t) { cout << "입력 :: " << t << endl; if(t == 0) break; } }
operator void*() const;
cin.clear(); // 플래그들을 초기화 하고
cin.ignore(100, '\n'); // 버퍼를 비워버린다
앞서 ios_base 클래스에서, 스트림의 입출력 형식을 바꿀 수 있다고 하였다. 예를들어서, 여태까지 수를 입력하면 10진수로 처리되었지만,
이번에는 16진수로 처리할 수 있는 법이다.
입력 받는 형식을 16진수로 바꿔준 함수는 보시다 싶이, 아래와 같은 스트림의 설정을 바꾸는 setf 함수 덕분이다.
cin.setf(ios_base::hex, ios_base::basefield);
std::ios_base& hex( std::ios_base& str );
istream& operator>> (ios_base& (*pf)(ios_base&));
str.setf(std::ios_base::hex, std::ios_base::basefield)
operator>> 에서 조작자를 받는다면 많은 일을 하는 것이 아니라, 단순히 pf(*this) 를 호출하게 된다. 호출된 hex 함수가 하는 일 또한 별로 없다. 이렇게, setf 를 사용하지 않더라도, 간단하게 조작자를 사용하면 훨씬 쉽게 입력 형식을 바꿀 수 있게 된다. 조작자들의 종류는 위에서 설명한 hex 말고도, 꽤 많은데,true 나 false 를 1 과 0 으로 처리하는 대신 문자열 그대로 입력 받는 boolalpha 도 있고, 출력 형식으로 왼쪽 혹은 오른쪽으로 정렬 시키는 left 와 right 조작자 등 여러가지가 있다. 그 외에도 아무 생각없이 사용하였던 endl 도 있다. endl 은 hex 와는 달리 출력을 관장하는 ostream 에 정의되어 있는 조작자로, 한 줄 개행문자를 출력하는 것 말고도, 버퍼를 모두 내보내는(flush) 역할도 수행한다. 앞서 말했듯이, 문자 1 개를 내보낸다고 해서 화면에 바로 출력되는 것이 아니라, 버퍼에 모은 다음에 버퍼에 어느정도 쌓이면 비로소 출력하게 된다. 이렇게 한다고 해서 대부분의 경우 문제되지는 않는데, 하지만 예를 들어 정해진 시간에 딱딱 맞춰서 화면에 출력해야 한다면, 이경우 버퍼에 저장할 필요없이 화면에 바로 내보내야 할 것이다. 이럴 경우를 위해서, 버퍼에 데이터가 얼마나 쌓여있든지 간에 바로 출력을 해주는 flush 함수가 있다. 따라서, endl 조작자는, 스트림에 '\n' 을 출력하는 것과 더불어 flush 를 수행해준다는 사실을 알 수 있다.
C++ 의 입출력 라이브러리에는 이에 대응되는 스트림 버퍼 클래스도 있는데, 이름이 streambuf 클래스다. 사실, 스트림이라 하면 그냥 쉽게 말해서 문자들의 순차적인 나열이라 보면 된다. 그냥 문자들이 순차적으로 쭈르륵 들어오는 것이 ==> stream 단어의 사전적 의미는 시냇물. 스트림이라 생각하시면 된다.
예를 들어서, 우리가 화면에 입력하는 문자도 스트림을 통해서 프로그램에 전달되는 것이고, 하드디스크에서 파일을 읽는 것도, 다른 컴퓨터와 TCP/IP 통신하는 것도 (결국 문자들을 쭈루륵 주고받는 것이니까), 모두 스트림을 통해 이루어진다는 것이다. 심지어 C++ 에서는 stringstream 을 통해서 평범한 문자열을 마치 스트림인 것 처럼 이용할 수 도 있게 해준다.
streambuf 클래스는 스트림에 대한 가장 기본적인 제어를 담당하고 있다.
streambuf 클래스는 스트림의 상태를 나타내기 위해서 세 개의 포인터를 정의하고 있다. 먼저 버퍼의 시작 부분을 가리키는 시작 포인터와, 다음으로 읽을 문자를 가리키고 있는 포인터 (우리가 흔히 말하는 스트림 위치 지정자), 그리고 버퍼의 끝 부분을 가리키고 있는 끝 포인터가 있다. streambuf 클래스는
입력 버퍼와 출력 버퍼를 구분해서 각각 get area 와 put area 라 부르는데, 이에 따라 각각을 가리키는 포인터도 g 와 p 를 붙여서 표현하게 된다.
char peek = cin.rdbuf()->snextc();
일단, hello world 를 친 다음, cin >> s 를 한 이후의 streambuf 의 상태다. 문자열의 경우 공백문자가 나오기 전 까지 읽어들이기 때문에 위와 같은 상태가 된다. 이제, snextc() 함수를 호출 했을 때 상태를 보자면
snextc() 함수가 스트림 위치 지정자를 한 칸 전진시키므로, 공백 문자를 띄어넘고, w 를 가리키게 된다.
그리고, 이에 해당하는 문자인 w 를 리턴하게 된다. 이 때 snextc 함수는 스트림 위치 지정자를 건드리지 않기 때문에,
cin >> s; cout << "다시 읽으면 : " << s << endl;에서 world 전체가 나오게 된다.
treambuf 에는 snextc 함수 말고도 수 많은 함수들이 정의되어 있다. 물론 이 함수들을 직접 사용할 일은 거의 없겠지만, C++ 입출력 라이브러리는 스트림 버퍼도 추상화해서 클래스로 만들었다는 것 정도로만 기억하면 좋을 것 같다. 또한, C++ 에서 streambuf 를 도입한 중요한 이유 한 가지는, 1 바이트 짜리 문자 뿐만이 아니라, wchar_t, 즉 다중 바이트 문자들 [UTF-8 같은 것]에 대한 처리도 용이하게 하기 위해서다.
예를 들어서, 다중 바이트 문자의 경우, 사용자가 문자 한 개만 요구했음에도 스트림에서는 1 바이트만 읽을 수 있고, 2 바이트, 심지어 4 바이트 까지 필요한 경우가 있다. C++ 에서는 이러한 것들에 대한 처리를 스트림 버퍼 객체 자체에서 수행하도록 해서, 사용자가 입출력 처리를 이용하는데 훨씬 용이하게 하였다.
'#Programming Language > C++' 카테고리의 다른 글
C++ Creating excel part 2. (0) | 2018.04.02 |
---|---|
C++ Creating excel part 1. (0) | 2018.04.02 |
C++ Virtual functions and inheritance. (0) | 2018.04.01 |
C++ Virtual function. (0) | 2018.04.01 |
C++ Standard String & Inheritance. (0) | 2018.04.01 |