【C++ primer】第8章 IO库 —— iostream,fstream,stringstream


Part II: The C++ Library
Chapter 8. The IO Library


前面章节已介绍了大部分 IO 库设施:

  • istream(输入流)类型,提供输入操作
  • ostream(输出流)类型,提供输出操作
  • cin,一个 istream 对象,从标准输入读取数据
  • cout,一个 ostream 对象,向标准输出写入数据
  • cerr,一个 ostream 对象,通常用于输出程序错误信息,写入到向标准错误
  • >> 操作符,用于从一个 istream 对象中读取输入数据
  • << 操作符,用于向一个 ostream 对象写入输出数据
  • getline 函数,从给定的 istream 读取一行输入数据到给定的 string

8.1 IO类

表8.1 IO库类型和头文件

头文件类型
iostreamistreamwistream 从流读取数据
ostreamwostream 向流写入数据
iostreamwiostream 读写流
fstreamifstreamwifstream 从文件读取数据
ofstreamwofstream 向文件写入数据
fstreamwfstream 读写文件
sstreamistringstreamwistringstream 从 string 读取数据
ostringstreamwostringstream 向 string 写入数据
stringstreamwstringstream 读写 string

为了支持使用宽字符的语言,该库定义了一组用于处理 wchar_t 数据的类型和对象。
宽字符版本的名称以 w 开头。例如,wcinwcoutwcerr 是分别对应于 cincoutcerr 的宽字符对象。
宽字符类型和对象与普通字符类型定义在相同的头文件中。

IO 类型之间的关系

概念上,设备类型和字符大小都不会影响要执行的 IO 操作。
该库使我们可以忽略不同类型的流之间的差异,这通过使用继承机制 (inheritance) 来实现。

简单来说,继承机制使我们可以声明一个特定的类继承自另一个类。通常,可以使用继承类的对象,像使用基类中同样类型的对象一样。

类型 ifstream 和 istringstream 都继承自 istream。因此,可以像使用 istream 对象一样来使用 ifstream 和 istringstream 对象。可以像使用 cin 一样使用这些类型的对象。
类似地,类型 ofstream 和 ostringstream 继承自 ostream。

IO对象无拷贝或赋值

ofstream out1, out2;
out1 = out2;              // error: cannot assign stream objects
ofstream print(ofstream); // error: can't initialize the ofstream parameter
out2 = print(out2);       // error: cannot copy stream objects

因为不能拷贝 IO 类型,所以流类型不能作为形参或返回类型。进行 IO 操作的函数通常使用引用传递和返回流。读写 IO 对象会改变它的状态,所以引用不能是 const。

条件状态

IO 类定义了一些函数和标志,可以访问和操作流的条件状态 (condition state)。

表8.2 IO库条件状态

函数或标志说明
strm::iostatestrm 是表8.1中列出的其中一种 IO 类型。iostate 是一种依赖机器的整型,代表流的条件状态。
strm::badbitstrm::iostate 值,用来指示流已崩溃
strm::failbitstrm::iostate 值,用来指示一个 IO 操作失败。
strm::eofbitstrm::iostate 值,用来指示流到达文件结束。
strm::gootbitstrm::iostate 值,用来指示流不处于错误状态。该值保证为零。
s.eof()若流 s 的 eofbit 置位,则返回 true。
s.fail()若流 s 的 failbit 或 badbit 置位,则返回 true。
s.bad()若流 s 的 badbit 置位,则返回 true。
s.good()若流 s 处于有效状态,则返回 true。
s.clear()将流 s 中所有条件值重置为有效状态。返回 void。
s.clear(flags)将 s 中的条件重置为 flags。flags 的类型是 strm::iostate。返回 void。
s.setstate(flags)添加指定的条件到 s。flags 的类型是 strm::iostate。返回 void。
s.rdstate()返回 s 的当前状态,返回类型为 strm::iostate。

确定一个流对象的状态的最简单方式是将这个对象作为条件来使用:

while (cin >> word)
	// ok: read operation successful . . .

询问流的状态

IO 库定义了一种依赖机器的整型 iostate,用来传达流的状态的相关信息。该类型以位集合类型方式来使用。

IO 类定义了四种 iostate 类型的 constexpr 值,表示特定的位模式。这些值用来表示特定类型的 IO 条件。

  • batbit 表示系统级错误,如不可恢复的读写错误。通常,一旦 badbit 置位,流就不可以使用了。
  • failbit 会在出现一个可恢复的错误后置位,比如在期望读取数值数据时读取字符。这种问题通常可以修正,继续使用流。
  • 到达文件结束位置时,eofbit 和 failbit 都会置位。
  • goodbit 值保证为 0,表示流未发生错误。
  • 如果 badbit,failbit 或 eofbit 置位,检测流的条件会失败。

管理条件状态

// remember the current state of cin
auto old_state = cin.rdstate();   // remember the current state of cin
cin.clear();                      // make cin valid
process_input(cin);               // use cin
cin.setstate(old_state);          // now reset cin to its old state 

// turns off failbit and badbit but all other bits unchanged
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

管理输出缓冲区

每个输出流管理一个缓冲区,用来保存程序读写的数据。

因为写入设备很耗时,所以允许操作系统将多个输出操作组合成一个写操作,这带来了很大的性能提升。

有几种情况会导致将缓冲区刷新(即写入)到实际的输出设备或文件:

  • 程序正常结束。作为从 main 函数 return 的一部分,所有输出缓冲区被刷新。
  • 在缓冲区变满时,将在写入下一个值之前将其刷新。
  • 可以使用操纵符如 endl 等显式刷新缓冲区。
  • 在每个输出操作后,使用 unitbuf 操纵符设置流的内部状态来清空缓冲区。默认情况下,cerr 是设置 unitbuf 的,所以写入 cerr 会立即刷新。
  • 一个输出流可能会关联到另一个流。在这种情况下,每当读写关联的流时,都会刷新关联流的缓冲区。默认情况下,cin 和 cerr 都与 cout 关联。因此,读取 cin 或写入 cerr 会刷新 cout 中的缓冲区。

刷新输出缓冲区

操纵符:

  • endl:完成换行,并刷新缓冲区
  • flush:刷新流,但不会添加字符到输出
  • ends:插入一个空字符到缓冲区,然后刷新缓冲区
cout << "hi!" << endl;   // writes hi and a newline, then flushes the buffer
cout << "hi!" << flush;  // writes hi, then flushes the buffer; adds no data
cout << "hi!" << ends;   // writes hi and a null, then flushes the buffer

unitbuf 操纵符

unitbuf 操纵符告诉流在每个随后的写入之后都刷新。nounitbuf 操纵符恢复流,使用正常的、系统管理的缓冲区刷新机制。

cout << unitbuf;       // all writes will be flushed immediately
// any output is flushed immediately, no buffering
cout << nounitbuf;     // returns to normal buffering

警告:如果程序崩溃,缓冲区不会刷新!

将输入流和输出流绑定在一起

当输入流绑定到输出流时,读取输入流会首先刷新与输出流关联的缓冲区。
库将 cout 连接到 cin,所以语句 cin >> ival; 导致 cout 关联的缓冲区被刷新。

注意:交互式系统通常应该将它们的输入流绑定到输出流。这样做,意味着所有输出,包括用户提示信息,都会在读取输入前写出来。

tie 有两个版本:

  • 一个版本不带参数,返回指向这个对象绑定的输出流的指针,如果没有绑定流,返回空指针。
  • 第二个版本接受一个指向 ostream 的指针,并将它本身绑定到这个 ostream。

可以绑定一个 istream 或 ostream 对象到另一个 ostream。

cin.tie(&cout);   // illustration only: the library ties cin and cout for us
// old_tie points to the stream (if any) currently tied to cin
ostream *old_tie = cin.tie(nullptr); // cin is no longer tied
// ties cin and cerr; not a good idea because cin should be tied to cout
cin.tie(&cerr);   // reading cin flushes cerr, not cout
cin.tie(old_tie); // reestablish normal tie between cin and cout

每个流一次最多只能绑定到一个流。但多个流可以将它们自己绑定到同一个 ostream。


8.2 文件输入和输出

除了继承自 iostream 类型中的行为外,定义在 fstream 中的类型增加了成员来管理与流关联的文件。

表8.3 fstream 特有的操作

操作说明
fstream fstrm;创建一个未绑定的文件流。fstream 指的是定义在 fstream 头文件的一个类型。
fstream fstrm(s);创建一个 fstream,打开名为 s 的文件。
s 可以是 string 类型或C风格字符串指针。这些构造函数都是 explicit。默认文件 mode 取决于 fstream 类型。
fstream fstrm(s, mode);与前一个构造函数类似,但以给定的 mode 打开 s。
fstrm.open(s)打开名为 s 的文件,并将文件与 fstrm 绑定。默认文件 mode 取决于 fstream 类型。返回 void。
fstrm.open(s, mode)以给定的 mode 打开 s。
fstrm.close()关闭与 fstrm 绑定的文件。返回 void。
fstrm.is_open()返回一个 bool,指示与 fstrm 关联的文件是否成功打开且尚未关闭。

使用文件流对象

ifstream in(ifile); // construct an ifstream and open the given file
ofstream out;       // output file stream that is not associated with any file

在C++11版本中,文件名可以是库 string 或C风格字符数组。以前版本的库只允许C风格字符数组。

用 fstream 替代 iostream&

ifstream input(argv[1]);   // open the file of sales transactions
ofstream output(argv[2]);  // open the output file
Sales_data total;          // variable to hold the running sum
if (read(input, total)) {  // read the first transaction
	Sales_data trans;      // variable to hold data for the next transaction
	while(read(input, trans)) {    // read the remaining transactions
		if (total.isbn() == trans.isbn()) //  check isbns
			total.combine(trans);  // update the running total
		else {
			print(output, total) << endl; //  print the results
			total = trans;         // process the next book
		}
	}
	print(output, total) << endl;  // print the last transaction
} else                             // there was no input
	cerr << "No data?!" << endl;

成员 open 和 close

ifstream in(ifile); // construct an ifstreamand open the given file
ofstream out;       // output file stream that is not associated with any file
out.open(ifile + ".copy");  // open the specified file 

if (out)     // check that the open succeeded
	// the open succeeded, so we can use the file

想要将一个文件流关联到另一个文件,必须首先关闭已关联的文件。

in.close();               // close the file
in.open(ifile + "2");     // open another file

自动构造和析构

// for each file passed to the program
for (auto p = argv + 1; p != argv + argc; ++p) {
	ifstream input(*p);   // create input and open the file
	if (input) {          // if the file is ok, "process" this file
		process(input);
	} else
		cerr << "couldn't open: " + string(*p);
} // input goes out of scope and is destroyed on each iteration

当一个 fstream 对象被销毁时,close 会自动被调用。

文件模式

表8.4 文件模式

模式说明
in以读方式打开
out以写方式打开
app每次写之前定位到文件末尾
ate打开文件后立即定位到文件末尾
trunc截断文件
binary以二进制方式进行 IO 操作

指定文件模式有以下限制:

  • out 只能设定于 ofstream 或 fstream 对象。
  • in 只能设定于 ifstream 或 fstream 对象。
  • 只有在指定 out 时,才能设定 trunc。
  • 只要没有设定 trunc,就可以设定 app 模式。如果指定 app,文件总是以输出模式打开,即使没有显式指定 out。
  • 默认情况下,以 out 模式打开的文件会被截断,即使没有指定 trunc。为了保存以 out 打开的文件的内容,可以指定 app,这样只会将数据写入到文件末尾;或者指定 in,这样文件可以同时用于输入和输出。
  • ate 和 binary 模式可用于任何文件流对象类型,且可与其他任何模式组合。

每个文件流类型的默认文件模式:

  • 与 ifstream 关联的文件以 in 模式打开;
  • 与 ofstream 关联的文件以 out 模式打开;
  • 与 fstream 关联的文件以 in 和 out 模式打开。

以 out 模式打开的文件会丢弃已存在的数据

// file1 is truncated in each of these cases
ofstream out("file1");   // out and trunc are implicit
ofstream out2("file1", ofstream::out);   // trunc is implicit
ofstream out3("file1", ofstream::out | ofstream::trunc);

// to preserve the file's contents, we must explicitly specify app mode
ofstream app("file2", ofstream::app);   // out is implicit
ofstream app2("file2", ofstream::out | ofstream::app);

8.3 string 流

表8.5 stringstream 特有的操作

操作说明
sstream strm;strm 是一个未绑定的 stringstream。sstream 是定义在 sstream 头文件的其中一个类型。
sstream strm(s);strm 是一个 sstream,存储 string s 的拷贝。这个构造函数是 explicit。
strm.str()返回 strm 存储的 string 的拷贝。
strm.str(s)复制 string s 到 strm 中。返回 void。

使用 istringstream

// members are public by default
struct PersonInfo {
	string name;
	vector<string> phones;
};

string line, word;  // will hold a line and word from input, respectively
vector<PersonInfo> people; // will hold all the records from the input
// read the input a line at a time until cin hits end-of-file (or another error)
while (getline(cin, line)) {
	PersonInfo info;      // create an object to hold this record's data
	istringstream record(line); // bind record to the line we just read
	record >> info.name;  // read the name
	while (record >> word)        // read the phone numbers
		info.phones.push_back(word);  // and store them
	people.push_back(info); // append this record to people
} 

输入内容类似这样:

morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000

使用 ostringstream

for (const auto &entry : people) {    // for each entry in people
	ostringstream formatted, badNums; // objects created on each loop
	for (const auto &nums : entry.phones) { // for each number
		if (!valid(nums)) {
			badNums << " " << nums;  // string in badNums
		} else            // "writes" to formatted's string
			formatted << " " << format(nums);
	}
	if (badNums.str().empty())      // there were no bad numbers
		os << entry.name << " "     // print the name
		   << formatted.str() << endl; // and reformatted numbers
	else                   // otherwise, print the name and bad numbers
		cerr << "input error: " << entry.name << " invalid number(s) " << badNums.str() << endl;
}

【C++ primer】目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值