《C++ Primer》第8章 IO库

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

8.1 IO类(P278)

我们目前使用过的 IO 对象(cincout)都是关联到控制台窗口、操纵 char 数据的。有时,我们需要对命名文件或者 string IO 操作。为了支持不同种类的 IO 操作,标准库定义了一些 IO 类型:

4867a8b265d1052cb95a861bed4a234

IO类型间的关系

概念上,设备类型和字符大小不会影响我们要执行的 IO 操作,例如我们可以用 >> 读取数据,这是通过**继承机制(inheritance)**实现的。

8.1.1 IO对象无拷贝或赋值(P279)

我们不能拷贝或对 IO 对象赋值,进行 IO 操作的函数通常以引用的方式传递和返回流。读写 IO 对象会改变其状态,所以传递和返回的引用不能是 const 的。

8.1.2 条件状态(P279)

IO 操作可能发生不同类型的错误,IO 类定义了一些函数和标志,可以帮助我们访问和操纵流的条件状态(condition state)

例如我们使用输入运算符并期待读入一个 int ,而实际上却得到了一个字符 B ,此时输入流将进入错误状态。只有当一个流处于无错状态时,我们才可以向它读写数据。最简单的判断流对象是否处于良好状态的方法:

while(cin >> word){ ... }

查询流的状态

IO 库定义了一个机器无关的 iostate 类型,提供表达流状态的完整功能。iostate 应该作为位集合来使用,IO 库定义了 4 个特定 constexpr 来表示特定的位:

  • badbit 表示系统级错误,通常为不可恢复的读写错误。
  • failbit 表示可恢复错误,如期望读取数值却得到字符。
  • eofbit 表示文件结束,此时 failbit 也会被置位。
  • goodbit0 表示流未发生错误(个人推测,前 3 个位在正常状态下为 0goodbit 就是它们的)。

标准库还定义了一组函数来查询上述标志位的状态:

  • good 在所有错误位均未被置位的情况下返回 true
  • badfaileof 在对应位被置位时返回 true ,此外,failbadbit 被置位时也会返回 true

管理条件状态

auto old_state = cin.rdstate();
cin.clear();
cin.setstate(old_state);
// 将failbit、badbit复位,其他位保持不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

上述代码中,rastate 函数返回一个 iostate 值,对应当前流的状态setstate 相当于把输入的 iostate “叠加”到流的原状态上,可以理解为或运算;无参数的 clear 复位所有错误标志位,带参数的 clear 接受一个 iostate 参数,表示流的新状态

8.1.3 管理输出缓冲(P281)

每个输出流都有一个缓冲区,用来保存程序读写的数据。保存在缓冲区中的数据可能随后再打印。

导致缓冲刷新(数据真正写到输出设备或文件)的原因:

  • 程序正常结束,作为 main 函数 return 操作的一部分。
  • 缓冲区满。
  • 使用操纵符(如 endl )显式刷新缓冲区。
  • 使用操纵符 unitbuf 设置流的内部状态。cerr 是默认 unitbuf 的,因此写到 cerr 里的内容是立即刷新的。
  • 和输入流关联的输出流在输入流读取数据前会刷新缓冲区。

刷新输出缓冲区

cout << "hi" << endl;    // 输出hi和一个换行,然后刷新缓冲区
cout << "hi" << ends;    // 输出hi和一个空字符,然后刷新缓冲区
cout << "hi" << flush;    // 输出hi,然后刷新缓冲区

unibuf操纵符

cout << unitbuf;    // 将流设置为:接下来每一次写操作之后都进行一次flush操作
cout << nounitbuf;    // 回到正常的缓冲方式

如果程序异常终止,缓冲区是不会刷新的。所以我们在调试崩溃的程序时,需要检查输出是否停留在缓冲区内。

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流中读取数据的操作都会先刷新输出流的缓冲区。标准库将 cincout 关联在一起。

// 将cin关联到cout(仅仅用来展示,相当于什么都没有做)
cin.tie(&cout);
ostream *old_tie = cin.tie();
// 让cin不与任何流关联
cin.tie(nullptr);
// 将cin关联到cerr
cin.tie(&cerr);
cin.tie(old_tie);

tie 的无参数版本返回指向输出流的指针,如果本对象关联到一个输出流,则返回那个输出流的指针;如果本对象没有关联的输出流,则返回 nullptrtie 的单参数版本接受一个指向输出流的指针,将自己关联到此输出流。

每个流最多与一个流关联,但多个流可以同时关联到一个输出流(单向关联)。

8.2 文件输入输出(P283)

头文件 fstream 定义了三个类型来支持文件 IO :

  • ifstream :从一个给定文件读取数据。
  • ofstream:向一个给定文件写入数据。
  • fstream :读写给定文件。

除了继承自 iostream 的操作外,fstream 中还定义了一些新的成员:

88282ac734807eb1312cf041dd6917f

8.2.1 使用文件流对象(P284)

当我们想要读写一个文件时,可以定义一个文件流对象,并将之与文件关联起来。每个文件流类都定义了名为 open 成员函数来定位并以指定模式打开文件。创建文件流对象时,如果我们提供了文件名,则 open 会自动调用:

ifstream in(ifile);    // 构造一个ifstream并打开文件
ofstream out;

在 C++ 新标准中,文件名可以是 string 对象或 C 风格字符数组,旧版本的标准库只允许 C 风格字符数组。

fstream代替iostream&

在要求使用基类对象的地方,我们可以用继承类对象代替。这意味着一个接受 iostream 类型引用或指针参数的函数,可以用相应的 fstream 对象代替。

成员函数openclose

ifstream in(ifile);    // 构造一个ifstream并打开文件
ofstream out;
out.open(ifile + ".copy");

如果 open 调用失败,fallbit 会被置位。检测 open 是否成功是个好习惯:

if(out)

当一个文件流与对应文件成功关联,我们称文件流已经打开。用一个已经打开的文件流调用 open 会导致 failbit 被置位。所以如果我们将文件流关联到另一个文件,我们必须先使用 close 关闭当前文件:

in.close();

自动构造和析构

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

8.2.2 文件模式(P286)

每个流都有一个关联的文件模式(file mode),用来指出如何使用文件:

8dd48df77fd3770cccad59711ee93e0

无论用哪种方式打开文件,我们都可以指定文件模式:

  • 只有 ofstreamfstream 能使用 out 模式。
  • 只有 ifstreamfstream 能使用 in 模式。( VS2022 似乎没这两条限制?这两条似乎与后面的冲突啊 … )
  • 只有当 out 模式被设定时,trunc 模式才能被设定。
  • 只要 trunc 模式没被设定,就可以设定 app 模式。在 app 模式下,文件综艺输出方式打开。
  • 默认情况下,以 out 模式打开的文件会默认被截断,为了保留文件内容,可以同时指定 addin 模式。
  • atebinary 模式可以用于任何类型的文件流对象。

每个文件流类型都有默认的文件模式,如果我们没有指定文件模式,就使用此模式。ifstream 默认使用 in 模式,ofstream 默认使用 out 模式,fstream 默认使用 inout 模式。

out模式打开会丢失数据

默认情况下,我们用 ofstream 对象打开文件时,文件内容的会被丢弃。

每次调用open时会确定文件模式

对于一个给定的流,每当打开文件时都可以改变文件模式。

8.3 string

sstream 头文件定义了三个支持内存 IO :

  • istringstream :从 string 读取数据。
  • ostringstream :向 string 写入数据。
  • stringstream :读写 string 数据。
00ba1c34d964732f0a72393165ce4ad

8.3.1 使用istringstream

假设我们要处理一个文件,文件的内容时人名和他们使用的电话号码:

morgan 2015552368 8625550123
drew 973550130
lee 6095550132 2015550175 800555000

我们首先定义一个类来保存数据:

struct PersonInfo {
	string name;
	vector<string> phones;
};

然后完成程序:

string line, word;
vector<PersonInfo> people;
while (getline(cin, line)) {
    PersonInfo info;
    istringstream record(line);
    record >> info.name;
    while (record >> word) {
        info.phones.push_back(word);
    }
    people.push_back(info);
}

8.3.2 使用ostringstream(P289)

接上面的例子,如果我们希望逐个验证电话号码并改变其格式 ,并将改变格式后的有效代码输出,并打印无效号码的错误信息:

for (const auto &entry : people) {
	ostringstream formatted, badNums;
	for (const auto &nums : entry.phones) {
		if (!valid(nums)) {
			badNums << " " << nums;
		}
		else {
			formatted << " " << format(nums);
		}
		if (badNums.str().empty())
			cout << entry.name << " " << formatted.str() << endl;
		else
			cerr << "input error: " << entry.name << "invalid number(s)" << badNums.str() << endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值