FILE
) 접근법을 확장하여 class
개념에 기반한 입/출력 (I/O) 라이브러리를 제공한다.
모든 C++ I/O 편의기능은 std
이름공간에 정의된다. std::
이름공간은 모호할 경우는 제외하고 앞으로 생략한다.
앞에서 C++ I/O 라이브러리를 사용하는 여러 예제를 보았다 (제 3장). 특히 삽입 연산자(<<)와 추출 연산자 (>>)를 보여주었다. 이 장은 I/O 기능을 더 자세하게 연구한다.
C++ 언어에서의 입출력 편의기능의 연구는 class
개념과 멤버 함수의 표기법을 많이 사용한다. 클래스 생성을 아직 다루지 않았다 (제 7장). 그리고 상속도 공식적으로 다루지 않았다 (제 13장). 그러나 I/O 편의기능을 미리 다루어 보고 나면 클래스 생성의 기술적 배경을 알 수 있을 것이다.
C++ I/O 클래스는 대부분 (basic_ios
처럼) basic_
으로 시작하는 이름이 있다. 그렇지만 이렇게 basic_
접두사가 붙은 이름은 C++ 프로그램에서 자주 볼수는 없다. 대부분의 클래스는 typedef
를 사용하여 정의되기 때문이다. 예를 들어:
typedef basic_ios<char> ios;C++는 다양한 문자 유형을 지원한다 (예,
char
, wchar_t
). I/O 편의기능은 템플릿 메커니즘을 사용하여 개발되었다. 전통적인 char
유형말고 다른 문자 유형으로 쉽게 변환할 수 있다. 제 21장에 더 정밀하게 다듬겠지만 이것으로 총칭 소프트웨어를 만들 수도 있다. 문자를 나타내는 어떠한 유형에도 사용이 가능하다. 그래서 위의 typedef
와 비슷하게 다음이 존재한다.
typedef basic_ios<wchar_t> wios;이렇게 하면
wchar_t
유형도 정의할 수 있다. 그래서 basic_
접두사는 흐름이 끊기지만 않는다면 C++ 주해서에서 생략했다. C++ 주해서는 주로 표준 8-비트 char
유형에 초점을 둔다.
iostream 객체는 표준 전방 선언을 사용하여 선언할 수 없다. 예를 들어:
class std::ostream; // 이제는 에러가 된다.대신에 iostream 클래스를 선언하려면
<iosfwd>
헤더를 포함해야 한다.
#include <iosfwd> // iostream 클래스를 선언하는 올바른 방법
C++ I/O를 사용하면 전통적으로 유형에 안전하다는 장점이 있다. 객체는 (또는 평범한 값들은) 스트림 안으로 삽입된다. 이것을 C에서 자주 맞이하는 상황에 비교해 보라. fprintf
함수는 어디에 어떤 종류의 값을 사용할지 형식화 문자열로 나타낸다. 이것을 다음의 상황과 비교해 보라. C++의 iostream 접근법은 값이 나타나는 곳에 객체를 다음과 같이 곧바로 사용한다.
cout << "There were " << nMaidens << " virgins present\n";컴파일러는
nMaidens
값의 유형을 알아채고 cout
iostream 안으로 삽입되는 문장 안의 적절한 곳에 적절한 값을 삽입한다.
이것을 C에서 만나는 상황에 비교해 보라. C 컴파일러는 나날이 똑똑해지고 있다. 설계가 잘된 C 컴파일러는 printf
서술문의 인자 목록에 상응하는 위치에서 형식 지정자와 변수의 유형이 서로 일치하지 않으면 경고해 준다. 그러나 경고해 주는 것 이상의 일을 별로 하지 못한다. 유형에 안전한 C++는 유형 불일치로부터 여러분을 지켜 준다. 일치시켜 볼 유형이 전혀 없기 때문이다.
이 외에도 iostream은 C에 사용된 표준 FILE
기반의 I/O와 거의 비슷하게 기능을 제공한다. 파일을 열고, 닫고, 위치를 찾고, 읽고, 쓰고 등등이 가능하다. C++에서 기본 FILE
구조는 C에 사용되는 것처럼 여전히 사용할 수 있다. 그러나 C++는 여기에 클래스 기반의 I/O가 추가되며 결과적으로 유형에 안전하고 확장성이 있으며 깔끔하게 디자인이 가능해진다.
ANSI/ISO 표준에서 그 의도는 I/O에 독립적인 골격구조를 세우는 것이었다. 이전의 iostream
라이브러리 구현이 언제나 표준을 따른 것은 아니다. 결과적으로 표준에 많은 확장이 추가되었다. 이전에 개발된 소프트웨어에서 I/O 부분은 부분적으로 재작성해야 할 가능성도 있다. 이것은 구형 소프트웨어를 수정해야 하는 이에게는 힘든 일이다. 그러나 ANSI/ISO 표준을 준수하는 I/O를 사용하여 이미 구축되어 있으므로 모든 특징과 확장을 쉽게 재구축할 수 있다. 이 모든 구현을 이 장에 다루는 것은 아니다. 많은 재구현이 상속과 다형성에 의존하기 때문이다. 이 주제는 공식적으로 제 13장과 14장에 다룬다. 선택된 재구현은 제 24장에 제공하고 이 장은 다른 장에 있는 적절한 항목을 참조하는 것으로 마무리한다.
class
ios_base
는 바탕 클래스이다. 그 위에 iostream
I/O 라이브러리가 구축되었다. 모든 I/O 연산의 핵심을 정의하고 있고 무엇보다도 I/O 상태의 조사를 위한 편의기능과 출력 형식화를 제공한다.
ios
클래스는 ios_base
으로부터 직접적으로 파생되었다. 입력과 출력을 수행하는 I/O 라이브러리의 모든 클래스는 자체가 이 ios
클래스로부터 파생된다. 그래서 그의 능력을 모두 상속 받는다 (묵시적으로 ios_base
의 능력도 상속된다). 독자 여러분은 이 장을 읽는 동안 이 사실을 명심해야 한다. 상속의 개념은 여기에서 다루지 않고 제 13장에 다룬다.
ios
클래스는 중요하다. 스트림에 사용되는 버퍼와의 통신을 구현하고 있기 때문이다. 이 버퍼는 streambuf
객체로서 아래의 장치와의 실제 I/O를 책임진다. 요약하면 iostream
객체는 자체로 I/O 연산을 수행하지 않고 그 임무를 연관된 (스트림) 버퍼 객체에게 맡긴다.
다음, 기본적인 C++ 출력 편의기능을 연구한다. 출력 연산에 사용되는 기본 클래스는 ostream
으로서 삽입 연산자는 물론 정보를 스트림에 쓰는 기타 편의기능들을 정의한다. 정보를 파일에 삽입하는 일 말고도 정보를 메모리 버퍼 안으로 삽입할 수 있다. 거기에 ostringstream
클래스를 사용할 수 있다. ios
클래스에 정의된 편의기능들을 최대한 사용하여 출력을 형식화한다. 뿐만 아니라 조작자를 사용하면 형식화 명령어를 직접적으로 스트림 안으로 삽입하는 것도 가능하다. 이런 관점에서 C++ 출력을 연구한다.
기본적인 C++ 입력 편의기능은 istream
클래스가 구현한다. 이 클래스는 추출 연산자와 관련 입력 편의기능을 정의한다. (ostringstream
을 사용하여) 정보를 메모리 버퍼 안으로 삽입하는 것에 비교하여 istringstream
을 사용하면 정보를 메모리 버퍼로부터 추출할 수 있다.
마지막으로, 여러 고급의 I/O 관련 주제를 연구한다. 예를 들어 같은 스트림으로부터 읽고 쓰는 것과 그리고 filebuf
객체로 C와 C++ I/O를 혼용하는 방법을 연구한다. 다른 I/O 관련 주제는 다른 곳에서 다룬다. 예를 들어 C++ 주해서의 제 24장을 참고하라.
streambuf
객체 사이의 인터페이스로서 streambuf
객체가 접근하는 장치와의 실제 입력과 출력을 책임진다.
이 접근법으로 새로운 종류의 장치에 새로운 종류의 streambuf
를 생성할 수 있다. `백전노장의' istream
클래스와 ostream
클래스의 편의기능과 조합하여 streambuf
를 사용할 수 있다. iostream
객체의 형식화 임무와 streambuf
객체로 구현된 외부 장치와의 버퍼링 인터페이스 사이의 차이를 이해하는 것이 중요하다. (소켓이나 파일 기술자 같은) 새 장치와의 인터페이스는 새로운 종류의 streambuf
를 생성하기를 요구한다. 새로운 종류의 istream
이나 ostream
객체를 요구하지 않는다. 그렇지만 istream
클래스나 ostream
클래스를 포장 클래스로 두르면 특수 장치에 쉽게 접근할 수 있다. 이 방식으로 stringstream
클래스를 만들었다.
iostream
과 관련된 여러 헤더 파일을 사용할 수 있다. 당면한 상황에 따라 다음 헤더 파일을 사용해야 한다.
iosfwd
: 스트림 클래스의 선언이 필요한 경우에만 소스에 이 헤더 파일을 포함해야 한다. 예를 들어 함수에 ostream
에 대한 참조 매개변수가 정의되어 있으면 컴파일러는 정확하게 ostream
이 무엇인지 알 필요가 없다. 그런 함수를 선언할 때 ostream
클래스만 선언하면 된다. 다음은 사용할 수 없다.
class std::ostream; // 에러 있는 선언 void someFunction(std::ostream &str);대신에 다음과 같이 사용해야 한다.
#include <iosfwd> // 올바르게 ostream 클래스 선언 void someFunction(std::ostream &str);
<ios>
: ios
클래스에 정의된 유형과 편의기능을 (아래 참고 ios::off_type
) 사용할 때 소스에 이 헤더를 포함해야 한다.
<streambuf>
: streambuf
클래스나 filebuf
클래스를 사용할 때 소스에 이 헤더를 포함해야 한다. 14.8절과 14.8.2항 참조.
<istream>
: istream
클래스를 사용하거나 입력과 출력을 모두 수행하는 클래스를 사용할 때 소스에 이 전처리기 지시어를 포함해야 한다. 6.5.1항 참조.
<ostream>
: ostream
클래스를 사용하거나 입력과 출력을 수행하는 클래스를 사용할 때 소스에 이 헤더를 포함해야 한다. 6.4.1항 참조.
<iostream>
: 전역 스트림 객체를 사용할 때 소스에 이 헤더를 포함해야 한다 (예를 들어 cin
과 cout
).
<fstream>
: 파일 스트림 클래스를 사용할 때 소스에 이 헤더를 포함해야 한다. 6.4.2항과 6.5.2항 그리고 6.6.3항 참조.
<sstream>
: 문자열 스트림 클래스를 사용할 때 소스에 이 헤더를 포함해야 한다. 6.4.3항과 6.5.3항 참조.
<iomanip>
: 매개변수화된 조작자를 사용할 때 소스에 이 헤더를 포함해야 한다. 6.3.2항 참조.
std::ios_base
클래스는 모든 I/O 연산을 위한 토대이다. 무엇보다도 I/O 스트림의 상태를 조사하는 기능과 대부분의 출력 형식화 기능을 정의한다. I/O 라이브러리의 스트림 클래스는 모두 이 클래스부터 시작하여 ios
클래스를 통하여 파생되고 그의 모든 능력을 상속받는다. ios_base
는 모든 C++ I/O 기능이 건설되는 토대이기 때문에 여기에 C++ I/O 라이브러리의 첫 클래스로 소개한다.
C에서처럼 C++의 I/O는 언어의 일부가 아니다 (물론 C++에 관한 ANSI/ISO 표준에 포함된다). 미리 정의된 모든 I/O 편의기능을 기술적으로 무시할 수는 있지만 그렇게 하는 사람은 없다. 그러므로 I/O 라이브러리는 C++를 위한 사실상의 I/O 표준이다. 앞에서 언급했듯이 iostream 클래스 자체는 I/O 사건에 책임을 지지 않고 보조 클래스에게 맡긴다. streambuf
클래스나 그로부터 파생된 클래스에 위임하는 것이다.
직접적으로 ios_base
객체를 생성하는 것은 불가능할 뿐만 아니라 필수도 아니다. 생성은 언제나 클래스 계통도를 따라 내려오다가 std::ios
와 같은 한 객체를 생성하는 부작용으로 일어날 뿐이다. ios
는 iostream 계통도에서 다음을 차지한다 (그림 3 참고). 모든 스트림 클래스가 차례로 ios
로부터 상속을 받고 그리하여 또 ios_base
로부터 상속을 받는다. ios_base
와 ios
의 구별은 실제로 중요하지 않다. 그러므로 실제로 ios_base
의 편의기능은 ios
가 제공하는 편의기능으로 간주된다. 특정한 편의기능이 어느 클래스에 실제로 정의되어 있는지 궁금한 독자는 적절한 헤더를 참조해야 한다 (예를 들어 ios_base.h
와 basic_ios.h
를 참고하라).
std::ios
클래스는 ios_base
클래스로부터 직접적으로 파생된다. 사실상 C++ I/O 라이브러리의 모든 스트림 클래스가 건설되는 토대이다.
ios
객체를 직접적으로 생성할 수는 있지만 그런 일은 거의 없다. ios
클래스의 목적은 basic_ios
클래스의 편의기능을 제공하는 것이다. 그리고 streambuf
객체와 관련된 여러 편의기능을 새로 추가한다. 이 기능들은 ios
클래스의 객체가 관리한다.
다른 모든 스트림 클래스는 직접 또는 간접적으로 ios
로부터 파생된다. 이것은 제 13장에 설명했듯이 ios
와 ios_base
의 모든 편의기능을 다른 스트림 클래스에서도 사용할 수 있다는 뜻이다. 이런 추가 스트림 클래스를 다루기 전에 먼저 ios
클래스가 제공하는 편의기능들을 소개한다 (묵시적으로: ios_base
가 제공).
명시적으로 ios
를 포함해야 할 경우도 있다. 예를 들어 형식화 깃발 자체가 소스 코드에 참조될 경우가 그렇다 (6.3.2.2목).
ios
클래스는 여러 멤버 함수를 제공한다. 대부분 형식화에 관련이 있다. 자주 사용되는 다른 멤버 함수는 다음과 같다.
std::streambuf *ios::rdbuf()
:streambuf
객체를 포인터로 돌려준다.ios
객체와 장치 사이의 인터페이스를 형성한다. 이 인터페이스로ios
객체가 통신한다.streambuf
클래스에 관한 더 자세한 정보는 14.8절과 24.1.2항을 참고하라.
std::streambuf *ios::rdbuf(std::streambuf *new)
:
현재ios
객체가 또다른streambuf
객체와 연관된다.ios
객체의 원래streambuf
객체를 포인터로 돌려준다. 이 포인터가 가리키는 객체는stream
객체가 영역을 벗어나더라도 파괴되지 않는다.rdbuf
의 호출자가 소유하기 때문이다.
std::ostream *ios::tie()
:현재ios
객체에 묶인ostream
객체를 포인터로 돌려준다 (다음 멤버 참고). 반환 값이 0이면 현재ostream
객체가ios
객체에 전혀 묶여 있지 않다는 뜻이다. 자세한 내용은 6.5.5항을 참고하라.
std::ostream *ios::tie(std::ostream *outs)
:
ostream 객체를 현재ios
객체에 묶는다. 이것은 입력이나 출력이 현재ios
객체에 의해서 수행될 때마다 먼저ostream
객체가 비워진다는 뜻이다.ios
객체의 원래ostream
객체를 포인터로 돌려준다. 묶인 것을 풀려면 0을 인자로 건네면 된다. 예제는 6.5.5항을 참고하라.
ios::badbit
:이 깃발이 서 있으면 스트림과 인터페이스하는 동안 streambuf
객체의 레벨에서 불법적인 연산이 요구된 것이다. 예제는 아래의 멤버 함수를 참고하라.
ios::eofbit
:이 깃발이 서 있으면 ios
객체가 파일 끝을 감지했다는 뜻이다.
ios::failbit
:이 깃발이 서 있으면 스트림 객체가 수행한 연산이 실패했다는 뜻이다 (예를 들어 입력에 숫자가 없을 때 int
를 추출하려고 시도하는 경우). 이 경우 스트림 자체는 요청된 연산을 수행할 수 없다.
ios::goodbit
:다른 세 가지 조건 깃발이 모두 누워 있으면 이 깃발이 올라간다.
ios
객체의 상태를 결정하거나 조작하기 위하여 여러 조건 멤버 함수를 사용할 수 있다. 원래는 int
값을 돌려주었는데 지금은 bool
유형이 반환된다.
bool ios::bad()
:
스트림의badbit
가 세워져 있으면true
이고 그렇지 않으면false
이다.true
가 반환되면 스트림과 인터페이스하는 동안streambuf
객체의 레벨에서 불법적인 연산이 요청되었다는 뜻이다. 이것은 무슨 뜻인가?streambuf
의 행위를 예측하지 못한다는 뜻이다. 다음 예제를 연구해 보자:std::ostream error(0);여기에서, 작동하는streambuf
객체를 제공하지 않고ostream
객체를 생성한다. 이 `streambuf
' 객체는 절대로 적절하게 작동하지 않을 것이기 때문에 처음부터badbit
깃발이 올라간다.error.bad()
멤버 함수는true
를 돌려준다.
bool ios::eof()
:
파일 끝을 (EOF
) 감지하면true
값이 반환된다 (즉,eofbit
깃발이 올라간다). 그렇지 않으면false
를 돌려준다.cin
으로부터 한 줄씩 읽고 있다고 가정하자. 그러나 마지막 줄은 끝이\n
문자로 끝나지 않는다. 그 경우\n
가름자를 읽으려고 시도하는std::getline
은 먼저 파일 끝을 감지한다. 이 때문에eofbit
깃발이 세워지고cin.eof()
는true
를 돌려준다. 예를 들어std::string str
과main
이 다음 서술문을 실행한다고 가정하자:getline(cin, str); cout << cin.eof();그리고 다음과 같이 실행하면echo "hello world" | program값 0이 인쇄된다 (EOF가 감지되지 않았다). 그러나 다음과 같이 실행하면echo -n "hello world" | program값 1이 인쇄된다 (EOF를 감지했다).
bool ios::fail()
:
ios::bad()
함수가true
를 돌려주거나failbit
깃발이 세워져 있으면true
값이 반환된다. 그렇지 않으면false
가 반환된다. 위의 예제에서cin.fail()
은false
를 돌려준다. 마지막 줄이 가름자로 끝나든 말든 상관이 없다 (줄 하나를 이미 읽었기 때문이다). 그렇지만 또다른getline
을 실행하면failbit
깃발이 올라가고cin::fail()
은true
를 반환하게 된다. 요청된 스트림 연산이 실패하면 일반적으로fail
은true
를 돌려준다. 간단한 예를 보여준다. 입력 스트림에 텍스트hello world
가 담겨 있을 때int
를 추출하려고 시도해 보자. 스트림 객체의bool
번역의 결과not fail()
값이 반환된다 (아래 참고).
ios::good()
:
goodbit
깃발의 값이 반환된다. 다른 조건 깃발들이 (badbit
,eofbit
,failbit
) 세워져 있지 않으면true
와 같다. 다음의 작은 프로그램을 연구해 보자:#include <iostream> #include <string> using namespace std; void state() { cout << "\n" "Bad: " << cin.bad() << " " "Fail: " << cin.fail() << " " "Eof: " << cin.eof() << " " "Good: " << cin.good() << '\n'; } int main() { string line; int x; cin >> x; state(); cin.clear(); getline(cin, line); state(); getline(cin, line); state(); }이 프로그램이 파일을 처리할 때 파일 안에 두 줄이 들어 있다.hello
줄과world
줄이 그것인데, 두 번째 줄이\n
문자로 끝나지 않으면 결과는 다음과 같다.Bad: 0 Fail: 1 Eof: 0 Good: 0 Bad: 0 Fail: 0 Eof: 0 Good: 1 Bad: 0 Fail: 0 Eof: 1 Good: 0그리하여x
를 추출하는 것은 실패한다 (good
은false
를 돌려준다). 다음, 에러 상태를 소거한다. 그리고 첫 줄은 성공적으로 읽는다 (good
은true
를 돌려준다). 마지막으로 (불완전하게) 두 번째 줄을 읽는다.good
은false
를 돌려주고eof
는true
를 돌려준다.
bool
값으로 번역하면:
스트림은 논리 값을 기대하는 표현식에 사용할 수 있다. 다음은 몇 가지 예이다.if (cin) // cin 자체는 추출후에 if (cin >> x) // 부울 값으로 번역된다. if (getline(cin, str)) // cin을 돌려주는 getline스트림을 논리 값으로 번역하면 실제로는 `not fail()
'로 번역된다. 그러므로 위의 예제는 다음과 같이 재작성해도 된다.if (not cin.fail()) if (not (cin >> x).fail()) if (not getline(cin, str).fail())그렇지만 앞의 형태가 거의 단독으로 사용된다.
에러 조건을 관리하기 위해 다음 멤버들을 사용할 수 있다.
void ios::clear()
:
에러 조건이 발생하고 그리고 그 조건을 고칠 수 있으면clear
를 사용해 그 파일의 에러 상태를 소거할 수 있다. 깃발들의 현재 상태를 먼저 소거한 후에 상태 깃발을 받아 설정하는 중복정의 버전이 존재한다.clear(int state)
버전이 그것으로서 반환 유형은void
이다.
ios::iostate ios::rdstate()
: ios
객체에 현재 설정된 깃발 집합을 (int
로) 돌려준다. 특정 깃발을 테스트하려면 비트 연산자를 사용하라:if (!(iosObject.rdstate() & ios::failbit)) { // 마지막 연산은 실패하지 않았다. }이 테스트는goodbit
깃발에 실행할 수 없음을 눈여겨보라. 그 값이 0이기 때문이다. 상태가 `good'인지 테스트 하려면 다음과 같이 사용하자:if (iosObject.rdstate() == ios::goodbit) { // 상태는 `good' }
void ios::setstate(ios::iostate state)
:
setstate
를 사용하면 스트림에 일정한 상태 집합을 할당할 수 있다. 반환 유형은void
이다. 예를 들어,cin.setstate(ios::failbit); // 상태를 `실패(fail)'로 설정여러 깃발을 한 번의setstate()
호출로 설정하려면bitor
연산자를 사용하라:cin.setstate(ios::failbit | ios::eofbit)
clear
멤버는 모든 에러 깃발을 지우는 단축 함수이다. 물론, 깃발을 지운다고 해서 그것이 곧 자동으로 에러 조건도 소멸된다는 뜻은 아니다. 전략은 다음과 같아야 한다.
clear
멤버를 호출한다.
출력 필드나 입력 버퍼의 너비를 설정할 필요가 있을 때 그리고 어느 값을 화면에 보여줄지 형식을 결정할 필요가 있을 때 형식화를 사용한다 (예를 들어 radix). 형식화 특징은 대부분 ios
클래스의 영역에 속한다. 형식화는 ios
클래스에 정의된 깃발로 제어된다. 이런 깃발들은 두 가지 방식으로 조작할 수 있다. 전문 멤버 함수를 사용하거나 아니면 조작자를 사용하는 것이다. 직접적으로 스트림으로부터 추출되거나 또는 스트림 안으로 삽입된다. 둘 중에 어떤 방법을 사용해야 하는지 특별한 이유는 없다. 두 방법 모두 가능하다. 다음 개관에서 다양한 멤버 함수를 먼저 소개한다. 다음에 깃발과 조작자를 다룬다. 깃발을 어떻게 조작하고 그 효과는 어떤지 보여주는 예제를 제공한다.
매개변수가 없는 조작자가 많다. 그리고 스트림 헤더 파일을 포함하면 하면 사용할 수있다 (즉, iostream
). 어떤 조작자는 인자를 요구하기도 한다. 인자를 요구하는 조작자를 사용하려면 iomanip
헤더를 포함해야 한다.
I/O 형식 깃발을 조작하기 위하여 여러 멤버 함수를 사용할 수 있다. 아래에 나열된 멤버를 사용하는 대신에 조작자를 사용할 수 있다. 직접적으로 스트림에 삽입하거나 추출할 수 있다. 사용 가능한 멤버를 알파벳 순서로 나열했다. 그러나 실제로 가장 중요한 멤버는 setf와 unsetf 그리고 width이다.
ios &ios::copyfmt(ios &obj)
:obj
의 모든 형식 깃발이 현재ios
객체로 복사된다. 현재ios
객체가 반환된다.
ios::fill() const
:현재 채움 문자를 돌려준다. 기본 값은 공간 문자이다.
ios::fill(char padding)
:
채움 문자를 재정의한다. 이전 채움 문자를 돌려준다. 이 멤버 함수를 사용하는 대신에setfill
조작자를 직접적으로ostream
에 삽입할 수 있다. 예를 들어:cout.fill('0'); // '0'을 채움 문자로 사용 cout << setfill('+'); // '+'를 채움 문자로 사용
ios::fmtflags ios::flags() const
:
멤버 함수가 호출된 스트림의 형식화 상태를 제어하는 현재 깃발 집합을 돌려준다. 특정 깃발이 서 있는지 검사하려면bit_and
연산자를 사용하라. 예를 들어:if (cout.flags() & ios::hex) cout << "Integral values are printed as hex numbers\n"
ios::fmtflags ios::flags(ios::fmtflags flagset)
:
이전 깃발 집합을 돌려준다. 그리고flagset
에 의하여 깃발에 새 상태가 정의된다.bitor
연산자로 여러 깃발을 지정한다. 예제:// 표현을 십육진수로 변경한다. cout.flags(ios::hex | cout.flags() & ~ios::dec);
int ios::precision() const
:부동소수점 값을 출력할 때 사용할 유효 자릿수를 돌려준다 (기본값: 6자리).
int ios::precision(int signif)
:
실수 값을 출력할 때 사용할 유효 자릿수가signif
에 설정된다. 이전에 사용된 유효 자릿수를 돌려준다. 요청된 자릿수가signif
를 초과하면 그 숫자가 `과학적' 표기법으로 표시된다 (6.3.2.2목). 조작자:setprecision
. 예제:cout.precision(3); // 3 자리 정밀도 cout << setprecision(3); // 같음, 조작자를 사용 cout << 1.23 << " " << 12.3 << " " << 123.12 << " " << 1234.3 << '\n'; // 출력: 1.23 12.3 123 1.23e+03
Type std::put_time(std::tm const *tm, char const *fmt)
:put_time
함수를 사용하면 현재 시간을 (부분별로) 화면에 보여줄 수 있다. 시간 부분은std::tm
객체에 제공된다. 시간을 표시하는 방식은 형식화 문자열fmt
로 정의된다.다음 예제는 현재 날짜와 시간을 보여주기 위해
put_time
을 사용한다.put_time
함수는 스트림 조작자로 간주된다.iomanip
헤더를 먼저 선언해야put_time
함수를 사용할 수 있다.#include <iostream> #include <iomanip> #include <ctime> int main() { std::time_t tm {time(0)}; std::cout << std::put_time(std::localtime(&tm), "%c") << '\n'; } // 출력 예제: Sun Dec 20 15:05:18 2015put_time
함수는std::localtime
과gmtime
과 함께 20.1.5항에 더 자세하게 기술한다.
ios::fmtflags ios::setf(ios::fmtflags flags)
:
하나 이상의 형식화 깃발을 세운다 (여러 깃발을 조합하려면bitor
연산자를 사용하라). 이미 서 있는 깃발은 영향을 받지 않는다. 이전의 깃발 집합을 돌려준다. 이 멤버 함수를 사용하는 대신에setiosflags
조작자를 사용해도 된다. 예제는 6.3.2.2목에 제공한다.
ios::fmtflags ios::setf(ios::fmtflags flags, ios::fmtflags mask)
:
mask
에 언급된 모든 깃발을 내리고flags
지정된 깃발들을 올린다. 이전의 깃발 집합을 돌려준다. 다음은 몇 가지 예제이다 (좀 더 깊은 연구는 6.3.2.2목을 참고하라):// 필드가 더 넓으면 정보를 왼쪽으로 정돈한다. cout.setf(ios::left, ios::adjustfield); // 정수 값을 십육진 수로 보여준다. cout.setf(ios::hex, ios::basefield); // 부동 소수점 값을 과학적 표기법으로 보여준다. cout.setf(ios::scientific, ios::floatfield);
ios::fmtflags ios::unsetf(fmtflags flags)
:
지정된 형식화 깃발을 내린다 (나머지 깃발들은 건드리지 않는다). 그리고 이전의 깃발 집합을 돌려준다. 활성 상태의 기본 깃발을 내리라는 요청은 무시된다 (예를 들어,cout.unsetf(ios::dec)
). 이 멤버 함수를 사용하는 대신에resetiosflags
조작자를 사용해도 된다. 예제:cout << 12.24; // 출력 12.24 cout << setf(ios::fixed); cout << 12.24; // 출력 12.240000 cout.unsetf(ios::fixed); // 이전의 ios::fixed 설정을 복구한다. cout << 12.24; // 출력 12.24 cout << resetiosflags(ios::fixed); // unsetf 대신에 // 조작자를 사용한다.
int ios::width() const
:
다음 삽입에 사용할 현재 활성 상태의 출력 필드 너비를 돌려준다. 기본 값은 0으로서, `값을 쓰는데 필요한 문자를 얼마든지 허용한다'는 뜻이다.
int ios::width(int nchars)
:
다음 삽입 연산의 필드 너비를nchars
개의 문자로 설정한다. 이전에 사용된 필드 너비를 돌려준다. 이 설정은 영구적이지 않다. 삽입 연산이 끝날 때마다 0으로 재설정된다. 조작자:std::setw(int)
. 예제:cout.width(5); cout << 12; // 5 문자의 필드 너비 사용 cout << setw(12) << "hello"; // 12 문자의 필드 너비 사용
정보를 넓게 보여주려면:
ios::internal
:
채움 문자를 (기본값은 공백) 음수의 음의 부호와 값 사이에 추가한다. 다른 값과 데이터 유형은 오른쪽으로 정돈된다. 조작자:std::internal
. 예제:cout.setf(ios::internal, ios::adjustfield); cout << internal; // 같다. 조작자 사용 cout << '\'' << setw(5) << -5 << "'\n"; // 출력 '- 5'
ios::left
:필드가 값의 너비보다 더 넓으면 값을 왼쪽으로 정돈하여 보여준다. 조작자:std::left
.cout.setf(ios::left, ios::adjustfield); cout << left; // 같다. 조작자 사용 cout << '\'' << setw(5) << "hi" << "'\n"; // 출력 'hi '
ios::right
:값보다 필드가 더 넓으면 값을 오른쪽으로 정돈한다. 조작자:std::right
. 이것이 기본값이다. 예제:cout.setf(ios::right, ios::adjustfield); cout << right; // 같다. 조작자 사용 cout << '\'' << setw(5) << "hi" << "'\n"; // 출력 ' hi'
다양한 숫자 표현을 사용하기:
ios::dec
:정수 값을 십진수로 화면에 보여준다. 조작자:std::dec
. 이것이 기본값이다.cout.setf(ios::dec, ios::basefield); cout << dec; // 같다. 조작자 사용 cout << 0x10; // 16을 보여준다.
ios::hex
:정수 값을 십육진수로 보여준다. 조작자:std::hex
.cout.setf(ios::hex, ios::basefield); cout << hex; // 같다. 조작자 사용 cout << 16; // 10을 보여준다.
ios::oct
:정수 값을 팔진수로 보여준다. 조작자:std::oct
.cout.setf(ios::oct, ios::basefield); cout << oct; // 같다. 조작자 사용 cout << 16; // 20을 보여준다.
std::setbase(int radix)
:숫자 표현을 십진수나 십육진수 또는 팔진수로 변환하는 조작자이다.cout << setbase(8); // 팔진수이면 8, 십진수이면 10, 십육진수이면 16을 밑수로 사용한다. cout << 16; // 20을 보여준다.
값을 세밀하게 조율하여 보여주기:
ios::boolalpha
:논리 값을 텍스트로 보여줄 수 있다.boolalpha
를 사용하여true
논리 값에는 텍스트 `true
'를 사용하고false
논리 값에는 텍스트 `false
'를 사용한다. 기본 값으로 이 깃발은 내려져 있다. 보조 깃발:ios::noboolalpha
. 조작자:std::boolalpha
그리고std::noboolalpha
.cout.setf(ios::boolalpha); cout << boolalpha; // 같다. 조작자 사용 cout << (1 == 1); // true를 보여준다.
ios::showbase
:정수 값의 밑수를 보여준다. 십육진 값에는0x
접두사가 사용되고 팔진 값에는 접두사0
이 사용된다. (기본값) 십진 값에는 접두사를 사용하지 않는다. 보조 깃발:ios::noshowbase
. 조작자:std::showbase
그리고std::noshowbase
.cout.setf(ios::showbase); cout << showbase; // 같다. 조작자를 사용한다. cout << hex << 16; // 출력 0x10
ios::showpos
:양의 십진수에+
부호를 보여준다. 보조 깃발:ios::noshowpos
. 조작자:std::showpos
그리고std::noshowpos
.cout.setf(ios::showpos); cout << showpos; // 같음, 조작자 사용 cout << 16; // +16을 보여준다. cout.unsetf(ios::showpos); // showpos를 복구한다. cout << 16; // 16을 보여준다.
ios::uppercase
:십육진 값의 기호를 대문자로 보여준다. 보조 깃발:ios::nouppercase
. 조작자:std::uppercase
그리고std::nouppercase
. 기본값으로 소문자가 사용된다.cout.setf(ios::uppercase); cout << uppercase; // 같음, 조작자 사용 cout << hex << showbase << 3735928559; // 0XDEADBEEF를 보여준다.
부동 소수점 수 보여주기
ios::fixed
:
고정 소수점수로 실수 값을 보여주려면 (예를 들어, 1.225e+01가 아니라 12.25로 보여주려면)fixed
형식화 깃발을 사용한다. 소수점 다음에 고정 자리수를 설정한다. 조작자:fixed
.cout.setf(ios::fixed, ios::floatfield); cout.precision(3); // . 다음에 3 자리 지정 // 다른 방법: cout << setiosflags(ios::fixed) << setprecision(3); cout << 3.0 << " " << 3.01 << " " << 3.001 << '\n'; << 3.0004 << " " << 3.0005 << " " << 3.0006 << '\n' // 결과: // 3.000 3.010 3.001 // 3.000 3.001 3.001예제를 보면 3.0005는 0으로부터 반올림되어 3.001이 된다 (마찬가지로 -3.0005는 -3.001이 된다). 먼저 정밀도를 설정하고 다음에fixed
를 사용해도 효과가 같다.
ios::scientific
:
실수 값을 과학적 표기법으로 보여준다 (예를 들어, 1.24e+03). 조작자:std::scientific
.cout.setf(ios::scientific, ios::floatfield); cout << scientific; // 동일, 조작자를 사용 cout << 12.25; // 1.22500e+01를 보여준다.
ios::showpoint
:실수를 보여줄 때 뒤따르는 십진 소수점과 그리고 뒤따르는 십진 0들을 보여준다. 보조 깃발:ios::noshowpoint
. 조작자:std::showpoint
그리고std::noshowpoint
.cout << fixed << setprecision(3); // 점(.) 다음에 세 자리 cout.setf(ios::showpoint); // 깃발 설정 cout << showpoint; // 같음, 조작자 사용 cout << 16.0 << ", " << 16.1 << ", " << 16; // 출력: 16.000, 16.100, 16맨 마지막 16은 부동소수점수가 아니라 정수임을 눈여겨보라. 그래서 소수점이 없으며showpoint
는 아무 효과가 없다.ios::showpoint
가 활성화되어 있지 않으면 뒤에 따르는 0들은 폐기된다. 분수가 0일 경우에도 소수점은 폐기된다.cout.unsetf(ios::fixed, ios::showpoint); // 깃발을 내린다. cout << 16.0 << ", " << 16.1; // 출력: 16, 16.1
공백 처리와 스트림 비우기
std::endl
:새줄문자를 삽입하고 스트림을 비우는 조작자. 스트림을 비우는 일은 필수가 아니며 그러면 불필요하게 I/O 처리가 느려질 것이다. 스트림을 명시적으로 비우려는 의도가 있지 않은 한, ('\n'
대신에)endl
을 사용하는 것은 피해야 한다. 스트림은 자동으로 비워짐을 눈여겨보라. 프로그램이 끝나거나 스트림이 또다른 스트림에 `묶일' 때 자동으로 비워진다 (6.3절tie
참고).cout << "hello" << endl; // prefer: << '\n';
std::ends
:0-바이트를 스트림에 삽입하는 조작자. 메모리 스트림과 조합하여 사용된다 (6.4.3항).
std::flush
:스트림을 비운다. 스트림을 비우는 것은 필수가 아니다. 그러면 불필요하게 I/O 처리가 느려지기 때문이다. 명시적으로 요구되지 않는 한,flush
는 피하는 것이 좋다. 스트림은 자동으로 비워짐을 주목하라. 프로그램이 끝나거나 스트림이 또다른 스트림에 `묶일' 때 자동으로 비워진다 (6.3절tie
참고).cout << "hello" << flush; // 가능하면 피하라
ios::skipws
:
스트림으로부터 값을 추출할 때 앞에 있는 (공간, 탭, 새줄 등등의) 공백 문자들은 건너뛴다. 이것이 기본값이다. 깃발이 서 있지 않으면 앞의 공백 문자들은 건너뛰지 않는다. 조작자:std::skipws
.cin.setf(ios::skipws); // 설정을 소거하려면 // cin.unsetf(ios::skipws)를 사용 cin >> skipws; // 같음, 조작자 사용 int value; cin >> value; // 앞쪽의 빈 공간은 건너뛴다.
ios::unitbuf
:이 깃발이 들려진 스트림은 출력 연산마다 버퍼를 비운다. 스트림을 비우는 것은 필수가 아니다. 그렇게 하면 불필요하게 I/O 처리가 느려질 것이다. 명시적으로 스트림을 비우려는 의도가 아닌 한,unitbuf
를 올리는 것은 피해야 한다. 스트림은 자동으로 비워짐을 주목하라. 프로그램이 끝나거나 스트림이 또다른 스트림에 `묶일' 때 자동으로 비워진다 (6.3절tie
참고). 보조 깃발:ios::nounitbuf
. 조작자:std::unitbuf
,std::nounitbuf
.cout.setf(ios::unitbuf); cout << unitbuf; // 같음, 조작자 사용 cout.write("xyz", 3); // 비운 다음에 쓴다.
std::ws
:현재 파일 위치에 (공간, 탭, 새줄, 등등의) 공백 문자들을 모두 제거하는 조작자.ios::noskipws
깃발이 서 있더라도 이 깃발이 서 있으면 공백 문자들이 제거된다. 예를 들어 (입력에 네 개의 공간 문자와 그 다음에X
문자가 들어 있다고 가정한다):cin >> ws; // 공백을 건너뛴다. cin.get(); // 'X'를 돌려준다.
std::ostream
클래스에 기반한다. ostream
클래스는 정보를 스트림에 삽입하는 삽입 연산자 (<<)와 write
와 같이 형식이 없는 정보를 스트림에 쓰는 멤버가 있다.
ostream
클래스는 여러 다른 클래스를 위한 바탕 클래스로 작동한다. ostream
클래스의 모든 기능을 제공하지만 각자 특수한 기능을 보유한다. 이 절은 다음 클래스들을 연구한다.
ostream
클래스;
ofstream
클래스 (C의 fopen(filename, "w")
에 비교됨);
ostringstream
클래스 (C의 sprintf
함수에 비교됨).
ostream
클래스는 기본 출력 편의기능을 정의한다. cout
과 clog
그리고 cerr
객체는 모두 ostream
객체이다. 출력에 관련하여 ios
클래스가 제공하는 모든 편의기능을 ostream
클래스에서도 사용할 수 있다.
다음 ostream 생성자와 같이 ostream
객체를 정의할 수도 있다.
std::ostream object(std::streambuf *sb)
:
이 생성자는ostream
객체를 생성한다. 이 객체는 기존의std::streambuf
객체를 둘러싼 포장 객체이다. (예를 들어std::ostream out;
을 사용하여) 삽입을 위해 사용할 수 있도록 평범한ostream
객체를 정의하는 것은 불가능하다.cout
이나 친구 함수가 사용될 때 실제로는 미리 정의된ostream
객체를 사용하고 있는 것이다.ostream
객체는 실제 인터페이스를 처리하는 객체인 (역시 미리 정의된)streambuf
객체를 사용하여 표준 출력 스트림에 이미 인터페이스로 정의되어 있다.그렇지만 0-포인터를 건네는
ostream
객체를 정의하는 것은 가능하다. 그런 객체는 삽입에 사용할 수 없다 (다시 말해, 거기에 뭔가 삽입될 때ios::bad
깃발이 세워진다). 그러나 나중에streambuf
를 줄 수 있다. 그리하여 미리 생성해 놓고 사용은 적절한streambuf
를 사용할 수 있을 때까지 미룰 수 있다 (14.8.3항).
ostream
클래스를 C++ 소스에 선언하려면 <ostream>
헤더를 포함해야 한다. 미리 정의된 ostream
객체 (std::cin, std::cout
등등)을 사용하려면 <iostream>
헤더를 포함해야 한다.
ostream
클래스는 형식화 출력과 이진 출력을 모두 지원한다.
삽입 연산자 (<<)는 유형에 안전하게 ostream
객체에 값을 삽입한다. 이것을 형식화된 출력이라고 부른다. 컴퓨터 메모리에 저장된 이진 값들이 인간이 읽을 수 있는 ASCII 문자로 특정한 형식화 규칙에 맞게 변환되기 때문이다.
삽입 연산자는 정보를 받을 ostream
객체를 가리킨다. 보통의 << 연상 작용은 바뀌지 않는다. 그래서 다음과 같은 서술문을 만나면
cout << "hello " << "world";가장 왼쪽의 피연산자가 먼저 평가되고 (
cout
<< "hello "
) 그 다음에 실제로는 cout
객체인 ostream &
객체가 반환된다. 이제 서술문은 다음과 같이 줄어든다.
cout << "world";그리고 두 번째 문자열이
cout
에 삽입된다.
<< 연산자는 (재정의된) 변형이 많이 있다. 그래서 다양한 유형의 변수를 ostream
객체에 삽입할 수 있다. 중복정의 << 연산자가 있다. int
와 double
그리고 포인터 등등을 기대한다. 연산자마다 ostream
객체를 돌려준다. 그 안에 지금까지의 정보가 삽입되어 있다. 그래서 다음 삽입 때 즉시 그대로 사용할 수 있다.
스트림은 형식화 출력을 위한 편의기능이 없다. 예를 들어 C의 printf
와 vprintf
함수 같은 기능이 없다. 이런 편의기능을 스트림의 세계에 구현하는 것은 어렵지 않다. 그러나 printf
-류의 기능은 C++ 프로그램에 거의 필요하지 않다. 게다가 잠재적으로 유형에 안전하지 않다. 그래서 이 기능은 완전히 피하는 편이 더 좋다.
이진 파일을 써야 할 때 텍스트 형식화는 사용되지 않으며 필요하지도 않다. int
값은 일련의 날 바이트로 써야 하며 ASCII 숫자로 0부터 9까지 쓰면 안된다. ostream
객체의 다음 멤버들은 `이진 파일'을 쓸 수 있다.
ostream& put(char c)
:
문자 하나를 출력 스트림에 쓴다. 문자는 바이트이므로 이 멤버 함수는 문자 하나를 텍스트 파일에 쓰는 데에도 사용할 수 있다.
ostream& write(char const *buffer, int length)
:
위의char const *buffer
에 저장된 최대length
개의 바이트를ostream
객체에 쓴다. 바이트는 버퍼에 저장된 그대로 씌여지므로 형식화는 전혀 수행되지 않는다. 첫 인자가char const *
임에 주목하라. 다른 유형을 쓰려면 강제 유형 변환이 필요하다. 예를 들어int
를 형식이 없는 일련의 바이트 값으로 쓰려면 다음과 같이 하라:int x; out.write(reinterpret_cast<char const *>(&x), sizeof(int));
write
호출로 바이트는 아래의 하드웨어의 종료형에 맞는 순서대로 ostream
에 씌여진다. 큰값 종료형 컴퓨터는 최상위 바이트를 먼저 쓰고 작은 값 종료형 컴퓨터는 최하위 바이트를 먼저 쓴다.
ostream
객체가 위치 변경 능력을 지원하는 것은 아니지만 보통은 지원한다. 다시 말해 이전에 씌여진 스트림의 한 부분을 다시 쓸 수 있다는 뜻이다. 위치 변경은 데이터베이스 어플리케이션에서 자주 사용된다. 데이터 베이스에 있는 정보에 무작위로 접근할 수 있어야 하기 때문이다.
현재 위치는 다음 멤버로 얻고 변경한다.
ios::pos_type tellp()
:파일에서의 현재 (절대) 위치가 반환된다. 스트림에 쓸 다음 연산이 일어날 곳이다.
ostream &seekp(ios::off_type step, ios::seekdir org)
:스트림의 실제 위치를 변경한다. 이 함수는 바이트 갯수를 나타내는off_type
step
을 기대한다.org
를 기점으로step
만큼 현재 스트림의 위치가 이동한다.step
값은 음수나 0 또는 양수이다.뜀 값의 시발점
org
는ios::seekdir
열거체의 값이다. 값은 다음과 같다.파일의 끝 위치를 넘어서 요구하거나 써도 좋다.
ios::beg
:스트림의 시작 위치에 상대적으로 뜀 값을 계산한다. 이 값이 기본값이다.ios::cur
:스트림의 현재 위치에 상대적으로 뜀 값을 계산한다 (tellp
가 돌려준 그대로 사용된다).ios::end
:스트림의 끝 위치에 상대적으로 뜀 값을 번역한다.EOF
위치를 넘어서 쓰면 그 사이의 바이트들은 값이 0으로 채워진다. 즉, 널-바이트로 채워진다.ios::beg
앞 위치를 요구하면ios::fail
깃발이 세워진다.
ios::unitbuf
깃발이 서 있지 않는 한, ostream
객체에 씌여지는 정보는 곧바로 물리적 스트림에 씌여지지 않는다. 대신에 쓰기 연산을 하는 동안 내부 버퍼에 채워지고 가득 차면 그 때야 버퍼가 비워진다.
스트림의 내부 버퍼는 프로그램의 통제 아래에서 비울 수 있다.
ostream& flush()
:ostream
객체가 내부적으로 저장한 모든 정보는 버퍼에 쌓여 있다가ostream
객체가 인터페이스되어 있는 장치로 비워진다. 스트림은 다음과 같은 경우 자동으로 비워진다.
std::ofstream
클래스는 ostream
클래스로부터 파생된다. ostream
클래스와 능력이 같지만 쓰기 용으로 파일에 접근하거나 파일을 생성할 수 있다.
ofstream
클래스를 사용하려면 <fstream>
헤더를 포함해야 한다. fstream
을 포함하더라도 자동으로 표준 스트림 cin
과 cout
그리고 cerr
를 사용할 수 있는 것은 아니다. 표준 스트림을 사용하려면 iostream
을 포함해야 한다.
다음 생성자들을 ofstream
객체에 사용할 수 있다.
ofstream object
:
기본 생성자이다.ofstream
객체를 정의한다. 나중에open()
멤버로 실제 파일에 연관지을 수 있다 (아래 참고).
ofstream object(char const *name, ios::openmode mode = ios::out)
:
이 생성자는ofstream
객체를 정의하고 즉시mode
출력 모드로name
이라는 이름의 파일에 연관짓는다. 6.4.2.1목에 출력 모드를 개관한다.ofstream out("/tmp/scratch");
ofstream
을 여는 것은 불가능하다. 그 이유는 파일 기술자가 널리 운영 체제마다 갖추어져 있는 것은 아니기 때문이다. 불행하게도 파일 기술자는 (간접적으로) std::streambuf
객체와 함께 사용할 수 있다 (어떤 구현에서는 std::filebuf
객체와 사용된다. 이 역시 streambuf
이다). streambuf
객체는 14.8절에 다루고 filebuf
객체는 14.8.2항에 다룬다.
직접적으로 ofstream
객체를 파일에 연관짓는 대신에 먼저 그 객체를 생성한 다음에 열 수 있다.
void open(char const *name, ios::openmode mode = ios::out)
:
ofstream
객체를 실제 파일에 연관짓는다.open
을 호출하기 전에ios::fail
깃발이 서 있었고 열기에 성공한다면 그 깃발은 내려간다. 이미 열린 스트림을 여는 것은 실패한다. 스트림을 또다른 파일에 연관지으려면 먼저 닫아야 한다.ofstream out("/tmp/out"); out << "hello\n"; out.close(); // 비우고 닫는다. out.open("/tmp/out2"); out << "world\n";
void close()
:ofstream
객체를 닫는다. 닫힌 객체의ios::fail
깃발을 세운다. 파일을 닫으면 버퍼에 있는 모든 정보를 연관 파일로 비운다. 연관된ofstream
객체가 존재하기를 멈추면 파일은 자동으로 닫힌다.
bool is_open() const
:스트림이 적절하게 생성되었다고 간주한다. 그러나 아직 파일에 붙지는 않았다. 예를 들어,ofstream ostr
서술문이 실행되었다. 이제 그의 상태를good()
을 통하여 점검하면 0-아닌 (즉, OK) 값이 반환된다. 여기에서 `good' 상태는 스트림 객체가 적절하게 구성되었음을 나타낸다. 그렇다고 파일이 열려 있다는 뜻은 아니다. 스트림이 실제로 열려 있는지 점검하려면is_open
을 호출해야 한다.true
가 반환되면 스트림이 열려 있다는 뜻이다.#include <fstream> #include <iostream> using namespace std; int main() { ofstream of; cout << "of's open state: " << boolalpha << of.is_open() << '\n'; of.open("/dev/null"); // Unix 시스템 cout << "of's open state: " << of.is_open() << '\n'; } /* 출력: of's open state: false of's open state: true */
ofstream
(또는 istream
)을 생성하거나 열 때 다음의 파일 모드 또는 파일 깃발을 사용할 수 있다 (6.5.2항). 이 값들은 유형이 ios::openmode
이다. bitor
연산자를 사용하면 깃발들을 조합할 수 있다.
ios::app
:출력 명령어마다 스트림을 끝에 재배치한다 (아래 ios::ate
참고). 아직 존재하지 않으면 파일이 생성된다. 스트림을 이 모드로 열면 기존 파일의 내용은 유지된다.
ios::ate
:
파일의 끝에서 시작한다. 기존의 내용은 다른 어떤 깃발이 그 객체에게 그렇게 하라고 명령할 경우에만 유지된다는 것을 주목하라. 예를 들어ofstream out("gone", ios::ate)
은gone
파일을 덮어쓴다. 묵시적으로ios::out
가 덮어쓰기를 야기하기 때문이다. 기존의 파일을 덮어쓰는 것을 방지해야 한다면ios::in
모드를 지정해도 된다. 그렇지만ios::in
이 지정되면 파일이 이미 존재하고 있어야 한다.ate
모드는 맨처음에 파일을 끝에 위치시키는 일만 한다. 그 다음에 정보는 파일 중간에seekp
함수로 쓸 수 있다.app
모드가 사용되면 정보는 오직 파일 끝에만 씌여진다 (seekp
연산을 무시하는 효과가 있다).
ios::binary
:파일을 이진 모드로 연다 (MS-윈도우즈와 같이 텍스트와 이진 파일을 구분하는 시스템에 사용된다).
ios::in
:읽기 용으로 파일을 연다. 파일은 반드시 존재해야 한다.
ios::out
:쓰기 용으로 파일을 연다. 존재하지 않으면 생성된다. 이미 존재하면 파일이 덮어씌여진다.
ios::trunc
:빈 파일로 처음 시작한다. 기존의 파일 내용은 모두 소실된다.
in | out: 스트림은 읽기와 쓰기용이다. 그렇지만, 파일이 반드시 존재해야 한다. in | out | trunc: 스트림은 읽기 쓰기용이다. 먼저 빈 파일을 (재)생성한다.흥미로운 점은
ifstream
과 ofstream
그리고 fstream
클래스의 open
멤버에 ios::openmode
유형의 두 번째 매개변수가 있다는 것이다. 이와 대조적으로 두 개의 열거 값에 적용하면 bitor
연산자는 int
를 돌려준다. 그럼에도 어떻게 여기에 bitor
연산자를 사용할 수 있는지는 11.11절에 해답이 있다 .
stream
편의기능을 이용하여 정보를 메모리에 쓰려면 std::ostringstream
객체를 사용해야 한다. ostringstream
클래스는 ostream
클래스로부터 파생되기 때문에 ostream
의 모든 편의기능을 ostringstream
객체도 사용할 수 있다. ostringstream
객체를 정의하고 사용하려면 <sstream>
헤더를 포함해야 한다. ostringstream
클래스는 다음 생성자와 멤버를 제공한다.
ostringstream ostr(string const &init, ios::openmode mode = ios::out)
:
openmode
를ios::ate
로 지정하면ostringstream
객체는string init
으로 초기화된다. 그리고 나머지는ostringstream
객체에 추가된다.
ostringstream ostr(ios::openmode mode = ios::out)
:
이 생성자도 기본 생성자로 사용할 수 있다. 또, 지금까지 객체에 저장된 정보의 끝에다 (ios::app
를 사용하여) 강제로 추가하는 것도 허용한다.std::ostringstream out;
std::string str() const
:ostringstream
객체 안에 저장된 문자열의 사본을 돌려준다.
void str(std::string const &str)
:
현재 객체를 새로운 내용으로 다시 초기화한다.
ostringstream
클래스의 사용법을 보여준다. 여러 값들을 객체에 삽입한다. 다음, ostringstream
객체가 보유한 텍스트가 std::string
으로 저장된다. 그 다음 그의 길이와 내용이 인쇄된다. 그런 ostringstream
객체들은 `유형을 문자로' 변환에, 예를 들어 int
값을 텍스트로 변환하는 데 자주 사용된다. 형식화 깃발을 ostringstreams
과 함께 사용해도 된다. ostream
클래스의 일부이기 때문이다.
다음은 ostringstream
객체가 어떻게 사용되는지 보여준다.
#include <iostream> #include <sstream> using namespace std; int main() { ostringstream ostr("hello ", ios::ate); cout << ostr.str() << '\n'; ostr.setf(ios::showbase); ostr.setf(ios::hex, ios::basefield); ostr << 12345; cout << ostr.str() << '\n'; ostr << " -- "; ostr.unsetf(ios::hex); ostr << 12; cout << ostr.str() << '\n'; ostr.str("new text"); cout << ostr.str() << '\n'; ostr.seekp(4, ios::beg); ostr << "world"; cout << ostr.str() << '\n'; } /* 이 프로그램의 출력: hello hello 0x3039 hello 0x3039 -- 12 new text new world */
std::istream
클래스에 기반한다. istream
클래스는 스트림으로부터 정보를 추출하는 추출 연산자 (>>)와 비형식 정보를 읽는 특수 멤버를 정의한다. 예를 들어 istream::read
멤버는 스트림으로부터 비형식 정보를 읽는다.
istream
클래스는 여러 다른 클래스들을 위한 바탕 클래스로 기여한다. 파생 클래스들은 istream
클래스의 기능을 제공하면서도 따로 자신만의 특징을 제공한다. 지금부터 다음 클래스들을 연구한다.
istream
클래스;
ifstream
클래스 (C의 fopen(filename, "r")
과 비슷하다);
istringstream
클래스 (C의 sscanf
함수와 비슷하다).
istream
클래스는 기본적인 입력 편의기능을 정의한다. cin
객체는 istream
객체이다. ios
클래스가 정의한 입력과 관련된 모든 편의기능은 istream
클래스에서도 사용할 수 있다.
다음 istream 생성자를 사용하면 istream
객체를 정의할 수 있다.
istream object(streambuf *sb)
:
이 생성자로 기존의std::streambuf
객체를 둘러싼 포장자를 생성할 수 있다.ostream
객체와 비슷하게istream
객체는 맨 처음에 0-포인터를 부여해 정의할 수 있다. 자세한 연구는 6.4.1항과 14.8.3항을 참조하고 예제는 제 24장을 참고하라.
istream
클래스를 정의하려면 <istream>
헤더를 포함해야 한다. 미리 정의된 istream
객체인 cin
을 사용하려면 <iostream>
헤더를 포함해야 한다.
istream
클래스는 형식 그리고 비형식 이진 입력을 모두 지원한다. 추출 연산자(operator>>
)는 istream
객체로부터 유형에 안전하게 값을 추출한다. 이것을 이른바 형식화 입력이라고 부른다. 인간이 읽을 수 있는 ASCII 문자들은 특정한 형식화 규칙에 따라 이진 값으로 변환된다.
추출 연산자는 새로운 값을 받는 객체나 변수를 가리킨다. >>의 보통의 연관성은 그대로 바뀌지 않는다. 그래서 다음과 같은 서술문을 만나면
cin >> x >> y;가장 왼쪽의 두 피연산자가 먼저 평가된다 (
cin
>> x
). 실제로 cin
과 같은 객체인 istream &
객체가 반환된다. 이제 서술문은 다음과 같이 줄어든다.
cin >> y그리고
y
변수는 cin
으로부터 추출된다.
>> 연산자는 (중복정의) 변형이 많다. 그래서 다양한 유형의 변수들을 istream
객체로부터 추출할 수 있다. int
, double
, 문자열, 문자 배열, 포인터 등등의 추출에 중복정의 >> 버전을 사용할 수 있다. 문자열 또는 문자 배열 추출은 기본값으로 먼저 모든 공백 문자들을 건너뛴 다음, 연속되어 있는 비공백 문자들을 모두 추출한다. 추출 연산자 처리가 끝났으면 정보가 추출된 istream
객체가 반환된다. 그리고 그것을 다음 표현식에 나타나는 추가 istream
연산에 즉시 사용할 수 있다.
스트림은 (C의 scanf
와 vscanf
함수 같은) 형식화 입력을 위한 편의기능이 없다. 이런 편의기능을 스트림의 세계에 추가하는 것이 어렵지는 않지만 scanf
같은 기능은 C++ 프로그램에 거의 필요가 없다. 게다가 잠재적으로 유형에 안전하지 않기 때문에 형식화 입력은 완전히 피하는 편이 더 좋다.
이진 파일을 읽어야 할 때 정보는 형식화되지 않는다. 다시 말해, int
값은 있는 그대로 일련의 바이트 연속열로 읽어야 한다. ASCII 숫치 문자 0부터 9로 읽지 않는다. istream
객체로부터 정보를 읽는 데 다음 멤버 함수를 사용할 수 있다.
int gcount() const
:마지막 비형식 입력 연산이 입력 스트림으로부터 읽은 문자의 갯수를 돌려준다.
int get()
:다음으로 사용가능한 문자 하나를 무부호char
값으로int
반환 유형을 사용하여 돌려준다. 더 이상 문자가 없으면EOF
를 돌려준다.
istream &get(char &ch)
:다음 문자 하나를 입력 스트림으로부터 읽어 ch
에 저장한다. 이 멤버 함수는 스트림 자체를 돌려준다. 그 안을 들여다 보고 문자를 얻었는지 여부를 결정할 수 있다.
istream& get(char *buffer, int len, char delim = '\n')
:입력 스트림으로터 최대len - 1
개의 문자를buffer
에서 시작하는 배열에 읽어 들인다. 이 배열은 길이가 적어도len
은 되어야 한다.delim
가름자를 만나면 읽기도 멈춘다. 그렇지만 가름자 자체는 입력 스트림으로부터 제거되지 않는다.문자들을
buffer
에 저장했으므로buffer
에 저장된 마지막 문자 다음에 값이 0인 문자가 씌여진다.len - 1
개의 문자를 읽기 전에 가름자를 만나면 또는len - 1
개의 문자를 읽은 후에 가름자를 만나지 못하면eof
함수와fail
함수는 0(false
)을 돌려준다 (6.3.1항). 값이 0인 문자를 가름자로 지정해도 된다. 그러면 NTB 문자열을 (이진) 파일로부터 읽을 수 있다.
istream& getline(char *buffer, int len, char delim = '\n')
:이 멤버는get
멤버 함수와 비슷하게 작동하지만getline
함수는 스트림으로부터delim
가름자를 만나면 제거한다. 가름자 자체는 만나더라도buffer
에 저장되지 않는다. (len - 1
개의 문자를 읽기 전에)delim
이 없다면fail
멤버 함수와 어쩌면eof
멤버 함수도 참을 돌려준다.std::string
클래스도std::getline
함수를 제공한다. 이 함수가 일반적으로 여기에 기술한getline
멤버 함수보다 더 좋다 (5.2.4항).
istream& ignore()
:입력 스트림으로부터 한 문자를 건너뛴다.
istream& ignore(int n)
:입력 스트림으로부터 n
개의 문자를 건너뛴다.
istream& ignore(int n, int delim)
:최대n
개의 문자를 건너뛰지만 입력 스트림으로부터delim
을 제거한 후에 문자를 건너뛰는 것을 중지한다.
int peek()
:이 함수는 다음 입력 문자를 돌려준다. 그러나 실제로 입력 스트림으로부터 그 문자를 제거하지 않는다. 더 이상 문자가 없으면 EOF
를 돌려준다.
istream& putback(char ch)
:ch
문자를 입력 스트림 `끝에 추가한다'. 다음 문자로 다시 읽는다. 이것이 허용되지 않으면EOF
가 반환된다. 한 문자를 뒤에 추가하는 것은 문제가 없다. 예제:string value; cin >> value; cin.putback('X'); // 출력: X cout << static_cast<char>(cin.get());
istream &read(char *buffer, int len)
:
입력 스트림으로부터 최대len
개의 바이트를 버퍼로 읽어 들인다. 그 전에EOF
를 만나면 거기까지만 읽고eof
멤버 함수는true
를 돌려준다. 이 함수는 주로 이진 파일을 읽을 때 사용된다. 6.5.2항은 이 멤버 함수를 사용하는 예제를 보여준다.read
로 열람한 문자의 갯수를 결정하기 위해gcount()
멤버 함수를 사용할 수도 있다.
istream& readsome(char *buffer, int len)
:
입력 스트림으로부터 최대len
개의 바이트를 버퍼로 읽어 들인다. 모든 문자를 버퍼에 읽는다. 그러나 그 전에EOF
를 만나면 거기까지만 읽고ios::eofbit
또는ios::failbit
깃발은 들지 않는다.
istream &unget()
:
스트림으로부터 읽은 마지막 문자를 제자리에 되돌려 놓는다.
istream
객체가 위치 변경을 지원하는 것은 아니다. 그러나 지원하는 객체가 있다. 이것은 스트림에서 같은 부분을 반복적으로 읽을 수 있다는 뜻이다. 위치 변경은 데이터베이스 어플리케이션에 자주 사용된다. 데이터 베이스에 있는 정보에 무작위로 접근할 수 있어야 하기 때문이다.
현재 위치는 다음 멤버로 열람하고 변경할 수 있다.
ios::pos_type tellg()
:스트림의 현재 (절대) 위치를 돌려준다. 스트림의 다음 읽기 연산이 일어날 곳이다.
istream &seekg(ios::off_type step, ios::seekdir org)
:스트림의 실제 위치를 변경한다. 이 함수는 바이트 갯수를 나타내는off_type
step
을 기대한다.org
를 기점으로 이 바이트 갯수만큼 현재 스트림 위치가 이동한다.step
값은 음수이거나 0 또는 양수가 될 수 있다.뜀 값의 시발점인
org
는ios::seekdir
열거체의 값이다. 그의 값들은 다음과 같다.파일의 마지막 위치를 넘어서 요구해도 문제가 없다.
ios::beg
:스트림의 처음 위치에 상대적으로 뜀 값을 계산한다. 이 값이 기본값이다.ios::cur
:스트림의 현재 위치에 상대적으로 뜀 값을 계산한다 (tellp
가 돌려주는 값과 같다).ios::end
:스트림의 현재 끝 위치에 상대적으로 뜀 값을 계산한다.ios::beg
보다 앞을 요구하면ios::failbit
깃발이 올라간다.
std::ifstream
클래스는 istream
클래스로부터 파생된다. istream
클래스와 능력이 같지만 읽기용으로 파일에 접근할 수 있다.
ifstream
클래스를 사용하려면 <fstream>
헤더를 포함해야 한다. fstream
을 포함하더라도 자동으로 표준 스트림 cin
과 cout
그리고 cerr
를 사용할 수 있는 것은 아니다. 표준 스트림을 선언하려면 iostream
헤더를 포함하라.
ifstream
객체에 다음 생성자들을 사용할 수 있다.
ifstream object
:
기본 생성자이다.ifstream
객체를 정의한다. 나중에open()
멤버로 실제 파일에 연관지을 수 있다 (아래 참고).
ifstream object(char const *name, ios::openmode mode = ios::in)
:
이 생성자로ifstream
객체를 정의하고 즉시mode
입력 모드로 그것을name
이라는 이름에 파일에 연관지을 수 있다. 6.4.2.1목에 입력 모드를 개관한다. 예제:ifstream in("/tmp/input");
직접적으로 ifstream
객체를 파일에 연관짓는 대신에, 객체를 먼저 생성하고 나중에 열 수 있다.
void open(char const *name, ios::openmode mode = ios::in)
:
ifstream
객체를 실제 파일에 연관짓는다.open
함수를 호출하기 전에ios::fail
깃발이 서 있고 열기에 성공하면 그 깃발이 내려간다. 이미 열린 스트림을 열려고 하면 실패한다. 스트림을 또다른 파일에 연관지으려면 먼저 스트림을 닫아야 한다.ifstream in("/tmp/in"); in >> variable; in.close(); // closes in in.open("/tmp/in2"); in >> anotherVariable;
void close()
:ifstream
객체를 닫는다. 이 함수는 닫힌 객체의ios::fail
깃발을 든다. 파일을 닫으면 버퍼에 있는 정보가 모두 연관 파일로 비워진다. 연관ifstream
객체가 존재하기를 멈추면 파일은 자동으로 닫힌다.
bool is_open() const
:스트림이 적절하게 구성되어 있다고 간주한다. 그러나 아직 파일에 붙지 않았다. 예를 들어 다음ifstream ostr
을 실행했다. 이제good()
를 통하여 그의 상태를 점검하면 0이 아닌 (즉, OK) 값이 반환된다. 여기에서 `good' 상태는 스트림 객체가 적절하게 생성되었다는 뜻이다. 그렇다고 파일도 역시 열린다는 뜻은 아니다. 스트림이 실제로 열려 있는지 테스트 하려면is_open
을 호출해야 한다. 스트림이 열려 있으면true
를 돌려준다. 다음 6.4.2항의 예제를 참고하라. 다음 예제는 이진 파일로부터 읽는 예를 보여준다 (6.5.1.1목):#include <fstream> using namespace std; int main(int argc, char **argv) { ifstream in(argv[1]); double value; // 파일로부터 배정도 수를 날 이진 상태로 읽는다. in.read(reinterpret_cast<char *>(&value), sizeof(double)); }
stream
편의기능을 사용하여 메모리로부터 정보를 읽으려면 std::istringstream
객체를 사용해야 한다. istringstream
클래스는 istream
클래스로부터 파생되었기 때문에 istream
의 모든 편의기능은 istringstream
객체도 사용할 수 있다. istringstream
객체를 정의하고 사용하려면 <sstream>
헤더를 포함해야 한다. istringstream
클래스는 다음 생성자와 멤버를 제공한다.
istringstream istr(string const &init,
ios::openmode mode = ios::in)
:객체를 init
의 내용으로 초기화한다
istringstream istr(ios::openmode mode = ios::in)
기본 생성자로 사용되는 것이 보통이다.std::istringstream in;
void str(std::string const &str)
:현재 객체를 새로운 내용으로 다시 초기화한다.
istringstream
클래스의 사용법을 보여준다. 여러 값들을 이 객체로부터 추출한다. 예를 들어 istringstream
객체는 문자열을 int
값으로 변환하는 데 자주 사용된다 (C의 atoi
함수와 비슷하다). 형식 깃발을 istringstreams
와 함께 사용할 수도 있다. istream
클래스의 일부이기 때문이다. 예제에서 특히 seekg
멤버의 사용에 주목하라:
#include <iostream> #include <sstream> using namespace std; int main() { istringstream istr("123 345"); // 텍스트를 저장한다. int x; istr.seekg(2); // "12"를 건너뛴다. istr >> x; // int를 추출한다. cout << x << '\n'; // 출력한다. istr.seekg(0); // 처음부터 다시 시도한다. istr >> x; // int를 추출한다. cout << x << '\n'; // 출력한다. istr.str("666"); // 또다른 텍스트를 저장한다. istr >> x; // 그것을 추출한다. cout << x << '\n'; // 출력한다. } /* 이 프로그램의 출력: 3 123 666 */
fail
이면 true
를 반환한다) 회돌이로부터 빠져 나온다 (break
).
getline(istream &, string &)
은 istream &
을 돌려준다 (6.5.1.1목). 그래서 읽기와 테스트는 하나의 표현식으로 축소할 수 있다. 그럼에도 위의 모형은 일반적 사례를 대표한다. 그래서 다음 프로그램을 사용하면 cin
을 cout
으로 복사할 수 있다.
#include <iostream> using namespace::std; int main() { while (true) { char c; cin.get(c); if (cin.fail()) break; cout << c; } }
더 줄일 수도 있다. get
을 if
서술문과 조합하면 결과는 다음과 같다.
if (!cin.get(c)) break;그럼에도 이것은 여전히 기본 규칙을 따를 것이다. `먼저 읽어라. 테스트는 그 다음이다'.
단순히 파일만 복사하는 일은 별로 요구되지 않는다. 자주 요구되는 상황은 파일을 특정한 지점까지 처리한 다음에 예와 같이 파일의 나머지 정보를 복사하는 것이다. 다음 프로그램은 이것을 보여준다. ignore
를 사용하여 첫 줄을 건너뛰고 (예제를 위해 첫 줄은 기껏해야 길이가 80 문자라고 간주함) 두 번째 서술문은 또다른 중복정의 << 삽입 연산자 버전을 사용한다. streambuf
포인터가 스트림 안으로 삽입된다. 스트림의 streambuf *
를 돌려주기 때문에 rdbuf
멤버는 스트림의 내용을 ostream
안으로 삽입할 간단한 도구이다.
#include <iostream> using namespace std; int main() { cin.ignore(80, '\n'); // 첫 줄을 건너뛴다. 그리고... cout << cin.rdbuf(); // 나머지는 streambuf *를 통하여 복사한다. }
이런 식으로 스트림을 복사하는 것은 반드시 streambuf
객체가 존재한다고 전제하기 때문이다. 결론적으로 streambuf
의 특정화된 모든 객체와 함께 사용할 수 있다.
tie
멤버 함수를 사용하면 ostream
객체를 ios
객체에 연결할 수 있다. 연결하면 ios
객체에 입력 또는 출력 연산이 수행될 때마다 ostream
객체에 묶인 버퍼가 비워진다. 기본으로 cout
은 cin
에 묶인다 (cin.tie(cout)
를 사용). 이 묶기는 cin
에 연산을 요구할 때마다 cout
이 먼저 비워진다는 뜻이다. 결합을 깨려면 예제와 같이 ios::tie(0)
을 호출하면 된다.
스트림을 결합하는 또다른 유용한 예는 cerr
스트림과 cout
스트림을 묶는 것이다. 이렇게 결합하면 표준 출력과 에러 메시지가 생성되는 순간에 동기적으로 화면에 보여준다.
#include <iostream> using namespace std; int main() { cerr.tie(0); // 푼다. cout << "first (buffered) line to cout "; cerr << "first (unbuffered) line to cerr\n"; cout << "\n"; cerr.tie(&cout); // cout을 cerr에 묶는다. cout << "second (buffered) line to cout "; cerr << "second (unbuffered) line to cerr\n"; cout << "\n"; } /* 출력: first (unbuffered) line to cerr first (buffered) line to cout second (buffered) line to cout second (unbuffered) line to cerr */
스트림을 결합하는 또다른 방법은 스트림이 streambuf
객체를 사용하도록 만드는 것이다. 이것은 ios::rdbuf(streambuf *)
멤버 함수로 구현할 수 있다. 이런 식으로 두 스트림은 각자의 형식을 사용할 수 있다. 한 스트림은 입력에 사용하고 다른 스트림은 출력에 사용할 수 있다. 그리고 운영 체제의 호출 말고 스트림 라이브러리로 방향전환을 구현할 수 있다. 예제는 다음 절을 참고하라.
ofstream out(string const &name) { ofstream ret(name); // ofstream을 생성한다. return ret; // 반환 값을 최적화한다. } // 그러나 문제 없음. 이동을 지원하기 때문이다. int main() { ofstream mine(out("out")); // 반환 값을 최적화한다. // 그러나 문제 없음. 이동을 지원하기 때문이다. ofstream base("base"); ofstream other; base.swap(other); // 스트림 교체 문제 없음 other = std::move(base); // 스트림 이동 문제 없음 // other = base; // 이것은 실패한다. // 스트림에는 복사할당이 불가능하기 때문이다. }
ios::rdbuf
를 사용하면 스트림이 streambuf
객체를 공유하도록 강제할 수 있다. 그래서 한 스트림에 씌여지는 정보는 실제로 또다른 스트림에 씌여진다. 이것을 방향전환(redirection)이라고 부른다. 방향전환은 주로 운영 체제 수준에서 구현되지만 여전히 필요한 경우가 가끔 있다 (24.2.3항).
방향전환이 유용한 상황은 주로 에러 메시지를 표준 에러 스트림이 아니라 파일로 써야 할 때이다. 파일 기술자 번호 2로 나타낸다. 유닉스 운영 체제에서 bash
쉘을 사용하면 다음과 같이 이것을 실현할 수 있다.
program 2>/tmp/error.log이 명령어 이후로
program
이 쓰는 모든 에러 메시지는 화면에 나타나는 대신에 /tmp/error.log
파일에 저장된다.
다음은 streambuf
객체로 이것을 구현하는 예이다. program
은 에러 메시지를 쓸 파일의 이름을 정의한 인자를 기대한다고 간주하자. 다음과 같이 호출할 수 있다.
program /tmp/error.log프로그램은 다음과 같다. 소스에 설명을 달았다.
#include <iostream> #include <fstream> using namespace std; int main(int argc, char **argv) { ofstream errlog; // 1 streambuf *cerr_buffer = 0; // 2 if (argc == 2) { errlog.open(argv[1]); // 3 cerr_buffer = cerr.rdbuf(errlog.rdbuf()); // 4 } else { cerr << "Missing log filename\n"; return 1; } cerr << "Several messages to stderr, msg 1\n"; cerr << "Several messages to stderr, msg 2\n"; cout << "Now inspect the contents of " << argv[1] << "... [Enter] "; cin.get(); // 5 cerr << "Several messages to stderr, msg 3\n"; cerr.rdbuf(cerr_buffer); // 6 cerr << "Done\n"; // 7 } /* argv[1] 파일에 출력 cin.get()에 출력: Several messages to stderr, msg 1 Several messages to stderr, msg 2 프로그램의 끝에 출력: Several messages to stderr, msg 1 Several messages to stderr, msg 2 Several messages to stderr, msg 3 */
errlog
는 에러 메시지를 쓸 ofstream
이고 cerr_buffer
는 streambuf
를 가리키는 포인터로서 원래의 cerr
버퍼를 가리킨다.
cerr
은 이제 errlog
에 의하여 정의된 streambuf
에 쓴다. 아래에 설명하는 바와 같이 cerr
이 사용하는 원래 버퍼가 저장된다는 것이 중요하다.
main
이 끝날 때 errlog
객체가 파괴되기 때문이다. cerr
의 버퍼가 복구되지 않는다면 그 시점에서 cerr
는 존재하지 않는 streambuf
객체를 참조할 것이다. 그 때문에 예상치 못한 결과를 야기한다. 방향전환을 시작하기 전에 원래의 streambuf
를 확실하게 저장하고 방향전환이 끝날 때 복구하는 것은 프로그래머의 책임이다.
Done
이 화면에 씌여진다. 방향전환이 끝났기 때문이다.
std::fstream
객체를 만들어야 한다. ifstream
객체와 ofstream
객체처럼 생성자는 열 파일의 이름을 받는다.
fstream inout("iofile", ios::in | ios::out);상수
ios::in
과 ios::out
를 사용한 것을 눈여겨보라. 파일이 읽기와 쓰기로 열려야 함을 알리고 있다. bitor
연산자로 결합하여 다중 모드 표시자를 사용할 수도 있다. 다른 방법으로 ios::out
대신에 ios::app
를 사용하고 단순히 (파일의 끝에) 쓰기만 할 수도 있다.
같은 파일에 읽고 쓰는 것은 언제나 약간 어색하다. 파일이 존재하지 않으면 어떻게 할까. 이미 존재하면 더 쓸 수는 없는 건가? 이런 문제를 가지고 좀 씨름을 한 후에 필자는 이제 다음과 같은 접근법을 사용한다.
#include <fstream> #include <iostream> #include <string> using namespace std; int main() { fstream rw("fname", ios::out | ios::in); if (!rw) // 파일이 아직 존재하지 않는다. { rw.clear(); // 다시 시도, ios::trunc를 사용하여 생성한다. rw.open("fname", ios::out | ios::trunc | ios::in); } if (!rw) // 여전히 생성할 수 없으면 벗어난다. { cerr << "Opening `fname' failed miserably" << '\n'; return 1; } cerr << "We're at: " << rw.tellp() << '\n'; // 무언가를 쓴다. rw << "Hello world" << '\n'; rw.seekg(0); // 다시 돌아가 무엇을 썼는지 읽는다. string s; getline(rw, s); cout << "Read: " << s << '\n'; }
이 접근법에서 첫 번째 생성 시도가 실패하면 fname
이 아직 존재하지 않는 것이다. 그러나 ios::trunc
깃발을 사용하여 open
을 시도해 볼 수 있다. 이미 파일이 존재하면 생성에 성공할 것이다. rw
를 정의할 때 ios::ate
를 지정하면 최초의 읽기/쓰기 행위는 기본으로 EOF
에서 일어난다.
DOS-류의 운영 체제는 \r\n
문자 연속열을 사용하여 텍스트 파일의 줄을 구분하므로 ios::binary
깃발이 있어야 이진 파일을 처리할 수 있다. 확실하게 \r\n
조합을 두 개의 문자로 처리한다. 일반적으로 (텍스트가 아닌) 이진 파일을 처리하려면 ios::binary
를 지정해야 한다. 기본값으로 파일은 텍스트 파일로 열린다. 유닉스 운영 체제는 텍스트 파일을 이진 파일과 구분하지 않는다.
fstream
객체에서 파일 깃발을 조합하면 스트림이 열릴 때 빈 채로 생성될지 아니면 비지 않은 채로 (재)생성될지 확인할 수 있다. 자세한 것은 6.4.2.1목을 참고하라.
파일이 읽기와 쓰기 모드로 열리면 << 연산자로 정보를 파일에 삽입할 수 있다. 반면에 >> 연산자를 사용하면 파일로부터 정보를 추출할 수 있다. 이런 연산은 순서에 상관없이 수행할 수 있지만 삽입과 추출 사이를 전환할 때는 seekg
나 seekp
가 필요하다. 위치 이동 연산을 사용하면 읽기와 쓰기에 사용된 스트림의 데이터를 활성화할 수 있다 (그리고 그 반대로 비활성화할 수 있다). fstream
객체의 istream
과 ostream
은 스트림의 데이터를 공유하고 위치 이동 연산을 수행함으로써 스트림은 자신의 istream
이나 ostream
부분을 활성화한다. 위치 이동 연산을 생략하면 읽은 후에 쓰기와 쓰기 후에 읽기는 그냥 실패한다. 다음 예제는 공백으로 분리된 단어를 파일로부터 읽어서 파일에서 방금 읽은 단어가 끝나는 곳을 넘어선 곳에 또다른 문자열을 쓰는 예를 보여준다. 마지막으로, 방금 씌여진 문자열이 끝난 곳을 넘어선 곳에 보이는 문자열을 더 읽는다.
fstream f("filename", ios::in | ios::out); string str; f >> str; // 첫 번째 단어를 읽는다. // 유명한 문구를 쓴다. f.seekg(0, ios::cur); f << "hello world"; f.seekp(0, ios::cur); f >> str; // 다시 읽는다.같은 파일에 읽기와 쓰기 (추출과 삽입) 연산을 전환할 때 seek 또는 clear 연산을 요구하기 때문에 하나의 표현식 서술문에 일련의 << 그리고 >> 연산을 실행하는 것은 불가능하다.
물론 삽입과 추출은 거의 사용되지 않는다. 일반적으로 삽입과 추출은 파일에서 잘 알려진 위치에서 일어난다. 그런 경우에 삽입과 추출이 요구되는 곳은 seekg
와 seekp
그리고 tellg
와 tellp
멤버로 제어할 수 있다 (6.4.1.2목과 6.5.1.2목 참고).
예를 들어 파일 끝을 넘어서 읽거나 파일 끝에 도달하거나 아니면 파일 시작 앞에 위치하기 때문에 일어나는 에러 조건은 clear
멤버 함수로 정리할 수 있다 (6.3.1항). clear
다음에 처리를 계속한다. 예를 들어,
fstream f("filename", ios::in | ios::out); string str; f.seekg(-10); // 이것은 실패한다. 그러나... f.clear(); // 계속해서 f를 처리한다. f >> str; // 첫 번째 단어를 읽는다.파일에 읽고 쓰는 상황은 데이터베이스 어플리케이션에서 볼 수 있다. 파일은 크기가 고정된 레코드로 구성되고 정보의 크기와 위치도 알려져 있다. 다음 프로그램은 텍스트 줄을 (존재하는) 파일에 추가한다. 파일에 줄 번호를 지정하면 특정 줄을 읽는 데에도 사용할 수 있다.
index
이진 파일을 사용하면 줄의 위치를 신속하게 열람할 수 있다.
#include <iostream> #include <fstream> #include <string> #include <climits> using namespace std; void err(char const *msg) { cout << msg << '\n'; } void err(char const *msg, long value) { cout << msg << value << '\n'; } void read(fstream &index, fstream &strings) { int idx; if (!(cin >> idx)) // 읽기 인덱스 { cin.clear(); // 다시 읽기 허용 cin.ignore(INT_MAX, '\n'); // 이 줄은 건너뛴다. return err("line number expected"); } index.seekg(idx * sizeof(long)); // 인덱스-오프셋으로 간다. long offset; if ( !index.read // 라인-오프셋을 읽는다. ( reinterpret_cast<char *>(&offset), sizeof(long) ) ) return err("no offset for line", idx); if (!strings.seekg(offset)) // 라인 오프셋으로 간다. return err("can't get string offset ", offset); string line; if (!getline(strings, line)) // 줄을 읽는다. return err("no line at ", offset); cout << "Got line: " << line << '\n'; // 줄을 보여준다. } void write(fstream &index, fstream &strings) { string line; if (!getline(cin, line)) // 줄을 읽는다. return err("line missing"); strings.seekp(0, ios::end); // 문자열로 위치 찾기 index.seekp(0, ios::end); // 인덱스로 위치 찾기 long offset = strings.tellp(); if ( !index.write // 오프셋을 인덱스에 쓴다. ( reinterpret_cast<char *>(&offset), sizeof(long) ) ) return err("Writing failed to index: ", offset); if (!(strings << line << '\n')) // 줄을 쓴다. return err("Writing to `strings' failed"); // 줄쓰기 확인 cout << "Write at offset " << offset << " line: " << line << '\n'; } int main() { fstream index("index", ios::trunc | ios::in | ios::out); fstream strings("strings", ios::trunc | ios::in | ios::out); cout << "enter `r <number>' to read line <number> or " "w <line>' to write a line\n" "or enter `q' to quit.\n"; while (true) { cout << "r <nr>, w <line>, q ? "; // 프롬프트를 보여준다. index.clear(); strings.clear(); string cmd; cin >> cmd; // cmd를 읽는다. if (cmd == "q") // cmd를 처리한다. return 0; if (cmd == "r") read(index, strings); else if (cmd == "w") write(index, strings); else if (cin.eof()) { cout << "\n" "Unexpected end-of-file\n"; return 1; } else cout << "Unknown command: " << cmd << '\n'; } }
파일을 읽고 쓰는 또다른 예제를 다음 프로그램에 보여준다. NTB 문자열을 처리하는 방법도 보여준다.
#include <iostream> #include <fstream> using namespace std; int main() { // 파일 읽기/쓰기 fstream f("hello", ios::in | ios::out | ios::trunc); f.write("hello", 6); // NTB 문자열을 2개 쓴다. f.write("hello", 6); f.seekg(0, ios::beg); // 파일의 처음으로 재설정한다. char buffer[100]; // 아니면: char *buffer = new char[100] char c; // 앞의 `hello'를 읽는다. cout << f.get(buffer, sizeof(buffer), 0).tellg() << '\n'; f >> c; // NTB 가름자를 읽는다. // 두 번째 `hello'를 읽는다. cout << f.get(buffer + 6, sizeof(buffer) - 6, 0).tellg() << '\n'; buffer[5] = ' '; // asciiz를 ' '으로 변환한다. cout << buffer << '\n'; // `hello'를 두 번 보여준다. } /* 생성된 출력: 5 11 hello hello */
streambuf
멤버를 사용하면 완전히 다른 방식으로 스트림을 읽고 쓰는 방법을 구현하는 것도 가능하다. 지금까지 언급한 모든 연구는 여전히 유효하다 (예를 들어, 쓰기 연산 다음에 읽기 연산을 하려면 먼저 seekg
를 사용해야 한다). streambuf
객체를 사용할 때 istream
객체가 또다른 ostream
객체의 streambuf
객체와 연관되거나 아니면 ostream
객체가 또다른 istream
객체의 streambuf
객체와 연관된다. 다음은 다시 이전의 프로그램이다. 이제는 연관 스트림을 사용한다.
#include <iostream> #include <fstream> #include <string> using namespace std; void err(char const *msg); // 이전 예제 참조 void err(char const *msg, long value); void read(istream &index, istream &strings) { index.clear(); strings.clear(); // 이전 예제의 read() 함수를 삽입한다. } void write(ostream &index, ostream &strings) { index.clear(); strings.clear(); // 이전 예제의 write() 함수를 삽입한다. } int main() { ifstream index_in("index", ios::trunc | ios::in | ios::out); ifstream strings_in("strings", ios::trunc | ios::in | ios::out); ostream index_out(index_in.rdbuf()); ostream strings_out(strings_in.rdbuf()); cout << "enter `r <number>' to read line <number> or " "w <line>' to write a line\n" "or enter `q' to quit.\n"; while (true) { cout << "r <nr>, w <line>, q ? "; // 프롬프트를 보여준다. string cmd; cin >> cmd; // cmd를 읽는다. if (cmd == "q") // cmd를 처리한다. return 0; if (cmd == "r") read(index_in, strings_in); else if (cmd == "w") write(index_out, strings_out); else cout << "Unknown command: " << cmd << '\n'; } }
이 예제에서 다음과 같은 사실을 알 수 있다.
streambuf
객체에 연관된 스트림들은 ifstream
객체도 아니고 ofstream
객체도 아니다. 알고 보면 istream
객체와 ostream
객체이다.
streambuf
객체는 ifstream
객체나 ofstream
객체에 의하여 정의되지 않는다. 대신에 스트림의 밖에서 다음과 같이 filebuf
와 생성을 사용하여 정의된다 (14.8.2항):
filebuf fb("index", ios::in | ios::out | ios::trunc); istream index_in(&fb); ostream index_out(&fb);
ifstream
객체는 ofstream
객체와 함께 사용되는 스트림 모드를 사용하여 생성할 수 있다. 역으로 ofstream
객체는 ifstream
객체와 함께 사용되는 스트림 모드를 사용하여 생성할 수 있다.
istream
과 ostream
이 streambuf
를 공유하면 읽기와 쓰기 포인터는 공유 버퍼를 가리킨다. 튼튼하게 묶여 있다.
fstream
객체보다 (별도의) 외부 streambuf
를 사용하면 장점은 (물론) 특정화된 streambuf
객체를 가진 stream
객체를 사용하는 길이 열린다는 것이다. 이런 streambuf
객체는 특정 장치를 제어하고 인터페이스하기 위해 특별히 생성할 수 있다. 이를 다듬는 일은 독자 여러분에게 연습 문제로 남겨둔다 (14.8절).