C++ Primer 总结索引 | 第八章:IO库

1、IO类

1、已经使用过的IO类型和对象 都是 操纵char数据的。默认情况下,这些对象 都是关联到 用户的控制台窗口的

但是 不能仅从控制台窗口 进行IO操作,应用程序 需要 读写命名文件,使用IO操作 处理string中的字符 会很方便,此外 引用程序还可能 读写需要 宽字符支持的语言

2、为了支持 这些不同种类的 IO处理操作,在istream和ostream之外,标准库 还定义了 其他一些IO类型

下表列出的类型 分别 定义在 三个独立的头文件中:iostream 定义了 用于 读写流的基本类型,fstream 定义了 读写命名文件的类型,sstream 定义了 读写内存string对象的类型

头文件类型
iostreamistream,wistream 从流读取数据;ostream,wostream 向流写入数据;iostream,wiostream 读写流
fstreamifstream,wifstream 从文件读取数据;ofstream,wofstream 向文件写入数据;fstream,wfstream 读写文件
sstreamistringstream,wistringstream 从string读取数据;ostringstream,wostringstream 向string写入数据;stringstream,wstringstream读写string

为了支持 使用宽字符的语言,标准库 使用了 一组类型和对象 来操纵 wchar_t 类型的数据

宽字符版本的类型 和 函数的名字 以一个w开始,例如:wcin、wcout和wcerr 是分别对应cin、cout和cerr的 宽字符版对象

宽字符版本的类型 和 对象 与其对应的普通char版本的类型 定义在 同一个头文件中。例如:头文件fstream定义了ifstream和wifstream类型

3、IO类型间的关系:设备类型和字符大小 都不会影响 要执行的IO操作。可以用 >> 读取数据,而不用管是 从一个控制台窗口,一个磁盘文件,还是一个 string 读取。类似的,也不用管 读取的字符 能存入 一个char对象内,还是需要 一个wchar_t对象来存储

标准库 使能忽略 这些不同类型的流之间 的差异,这是通过 继承机制 实现的。利用模板 可以使用 具有继承关系的类
继承机制 使可以 声明一个 特定的类 继承自另一个类。通常可以 将一个派生类(继承类)对象 当作其基类(所继承的类)对象来使用

类型ifstream和istringstream都继承自 istream,可以像 使用istream对象一样 来使用ifstream和istringstream对象。跟cin一样,可以对一个 ifstream 或 istringstream 对象调用 getline,也可以使用 >> 从一个 ifstream 或 istringstream 对象中 读取数据
类似的,ofstream 和 ostringstream都继承自 ostream

本节 剩下部分 所介绍的标准库流特性 都可以 无差别地应用于 普通流、文件流和string流,以及 char或宽字符流的 版本

1.1 IO对象 无拷贝或赋值

不能拷贝 或 对IO对象赋值

ofstream out1, out2;
out1 = out2; // 错误,不能对流对象 赋值
ofstream print(ofstream); // 错误,不能 初始化ofstream参数
out2 = print(out2); // 错误,不能拷贝流对象

由于 不能拷贝IO对象,也不能 将形参或返回类型 设置为 流类型,进行IO操作的函数通常以 引用方式传递 和 返回流。读写一个IO对象 会改变其状态,因此 传递和返回的引用 不能是const的

1.2 条件状态

1、IO类定义的 一些函数和标志,可以帮助 访问和操纵流的 条件状态

条件状态作用
strm::iostatestrm是一种IO类型,在上一张表中列出的都可以进行替换。iostate是一种机器相关的类型,提供了表达 条件状态 的完整功能
strm::badbitstrm::badbit 用来指出 流已崩溃
strm::failbitstrm::failbit 用来指出 一个IO操作失败了
strm::eofbitstrm::eofbit 用来指出 流到达了文件结束
strm::goodbitstrm::goodbit 用来指出 流未处于错误状态。此值保证为0
s.eof()若流s的eofbit置位,则返回true
s.fail()若流s的failbit或badbit置位,则返回true
s.bad()若流s的badbit置位(崩溃),则返回true
s.good()若流处于有效状态,则返回true
s.clear()将流s中所有条件状态位复位,将流的状态设置为有效,返回void
s.clear(flags)根据给定的flags标志位,将流s中对应条件状态位复位。flags的类型为strm::iostate,返回void
s.rdstate()返回流s的当前条件状态,返回值类型为strm::iostate

2、一个流一旦发生错误,其上后续的IO操作 都会失败

由于 流可能处于 错误状态,因此 代码通常应该 在使用一个流之前 检查它是否处于良好状态。确定一个流对象的状态 的最简单的方法是 将它当作一个条件来使用:while (cin >> word)

3、查询流的状态:需要知道 流为什么失败。例如:在键入文件结束标识后 应对措施 与 遇到一个IO设备错误的处理方式 是不同的

IO库定义了 一个与机器无关的iostate类型,它提供了 表达流状态的完整功能,这个类型 应作为一个位集合 来使用。IO库定义了4个iostate类型的constexpr值,表示 特定的 位模式。这些值 用来表示特定类型的 IO条件,可以 与位运算符一起使用 来一次性检测 或设置多个标志位

badbit 表示系统级错误,如 不可恢复的读写错误,如 不可恢复的读写错误。一旦 badbit 被置位,流就无法再使用了
在发生 可恢复错误后,failbit 被置位,如期望读取数值 却读出一个字符等 错误。这种错误是可以修正的,流还可以继续使用
如果 到达文件结束位置,eofbit 和 failbit 都会被置位
goodbit 的值为0,表示 流未发生错误
如果 badbit、failbit 和 eofbit 任一个被置位,则 检测流状态的条件 会失败

标准库 还定义了一组函数 来查询这些标志位的状态。操作good在所有错误位 均未置位的情况下 返回true,而 bad、fail 和 eof 则在对应错误位 badbit / failbit / eofbit 被置位时 返回true
在 badbit 被置位时,fail 也会返回 true,所以 使用 good 或 fail 是确定流的总体状态的正确办法。将流当作条件使用的代码 就等价于 !fail()。而 eof 和 bad 操作只能表示特定的错误

4、管理条件状态:流对象的 rdstate 成员 返回一个 iostate 值,对应流的当前状态。setstate操作 将给定条件位置位,表示 发生了对应错误。clear 不接受参数的版本 清除(复位)所有错误标志位

// 记住 cin 的当前状态
auto old_state = cin.rdstate(); // 记住cin的当前状态
cin.clear(); // 使cin有效
使用cin
cin.setstate(old_state); // 将cin置为原有状态

为了复位 单一的条件状态位,首先用 rdstate 读出当前条件状态,然后 用位操作 将所需位复位 来生成新的状态(如 将failbit 和 badbit 复位,但保持 eofbit 不变:cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

5、测试函数,调用参数为cin

#include <iostream>
#include <string>void iofunction(std::istream& is)
{std::string s;while (is >> s)std::cout << s << std::endl;is.clear();
}int main()
{iofunction(std::cin);std::string s1;while (std::cin >> s1) // 因为有is.clear()这里的cin可以正常使用std::cout << s1 << std::endl;return 0;
}

运行结果:
运行结果
6、什么情况下,下面的while循环会终止

while (cin >> i) /*  ...    */

badbit、failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败

1.3 管理输出缓冲

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

os << "please enter a value: ";

文本串可能立即被打印出来,但也有可能 被操作系统保存在 缓冲区中,随后 再打印。有了缓冲机制,操作系统 就可以将程序的多个输出操作组合成 单一的系统级写操作

由于 设备的写操作 可能很耗时,允许 操作系统将多个输出操作 组合为 单一的设备写操作 可以带来很大的性能提升

导致 缓冲区刷新(即,数据真正写出到 输出设备或文件)的原因有很多:
1)程序正常结束,作为 main函数的return操作的一部分,缓冲刷新 被执行
2)缓冲区满时,需要 刷新缓冲,而后新的数据 才能继续写入缓冲区
3)在每个输出操作之后,可以用 操纵符unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此 写到cerr的内容 都是立即刷新的
4)一个输出流 可能被关联到 另一个流。当 读写被关联的流时,关联到的流的缓冲区 会被刷新
例如:默认情况下,cin 和 cerr 都关联到 cout。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新

2、刷新输出缓冲流:endl 完成换行 并刷新缓冲区。IO库中 还有两个类似的操作符:flush 和 ends。flush 刷新缓冲区,但不输出任何额外的字符;ends 向缓冲区 插入一个空字符,然后刷新 缓冲区:

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

3、unitbuf 操作符:在每次输出操作后 都刷新缓冲区,可以使用 unitsbuf 操纵符。它告诉流 在接下来的每次写操作之后 都进行一次 flush操作。而 nounitbuf (+no)操纵符 则重置流,使其恢复 使用正常的系统管理的缓冲区刷新机制

cout << unitbuf; // 所有输出操作后 都会立即刷新缓冲区// 任何输出都立即刷新,无缓冲cout << nounitbuf; // 回到正常的缓冲方式

4、如果程序崩溃,输出缓冲区 不会被刷新,它所输出的数据 很可能停留在 缓冲区中 等待打印
当调试一个 已经崩溃的程序时,需要确认 那些认为已经输出的数据 确实已经刷新了。否则,可能将大量时间 浪费在 追踪代码为什么没有执行上,而实际上 代码已经执行了,只是 程序崩溃后 缓冲区没有被刷新,输出数据被挂起 没有打印

5、关联 输入和输出流:任何试图 从输入流读取数据的操作 都会先刷新 关联的输出流。标准库 将cout 和 cin关联在一起:cin >> ival; 导致cout 的缓冲区被刷新

交互式系统 通常应该 关联输入流 和 输出流。这意味着 所有输出,包括 用户提示信息,都会 在读操作之前 被打印出来

tie有两个重载的版本:
1)不带参数,返回指向 输出流的指针。本对象当前关联到 一个输出流,则 返回的就是 指向这个流的指针,如果对象 未关联到流,则 返回空指针
2)接受一个指向 ostream 的指针,将自己关联到此 ostream。x.tie(&o) 将流x关联到 输出流o,也是返回指向 输出流的指针(不是这次关联的,是之前关联的输出流)

既可以 将一个istream对象 关联到 另一个ostream,也可以 将一个ostream关联到 另一个ostream

cin.tie(&cout); // 仅仅是用来展示:标准库将cin和cout关联在一起// old_tie 指向当前关联到cin的流(不是nullptr,如果有的话,这里是cout)
ostream *old_tie = cin.tie(nullptr); // cin不再和其他流关联// 将cin与cerr关联;这不是一个好主意,因为cin应该关联到cout
cin.tie(&cerr); // 读取cin会刷新cerr而不是cout
cin.tie(old_tie); // 重建cin和cout见正常关联

为了将一个 给定的流 关联到 一个新的输出流,将 新流的指针 传递给了tie。为了彻底解开流的关联,我们传递了一个空指针。每个流同时最多关联到 一个流,但是 多个流 可以同时关联到 同一个 ostream

2、文件输入输出

1、头文件 fstream 定义了 三个类型 来支持文件IO:
1)ifstream 从一个给定文件 读取数据
2)ofstream 向一个给定文件 写入数据
3)fstream 可以读写给定文件

2、除了 继承自iostream类型的行为之外,fstream中定义的类型 还增加了一些新的成员 来管理与流关联的文件
可以对 fstream、ifstream 和 ofstream 对象调用这些操作(fstream特有),但不能对 其他IO类型调用这些操作

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

2.1 使用文件流对象

1、当想要 读写一个文件时,可以定义 一个文件流对象,并将 对象与文件关联起来。每个文件流类 都定义了一个名为open的成员函数,定位给定的文件,并视情况 打开为读 或 写模式

2、创建文件流对象时,可以提供文件名(可选)。如果提供文件名,则 open 会自动被调用
定义了输入流in,输出流out

ifstream in(ifile); // 构造一个 ifstream 并打开给定文件,ifile参数 为string类型的文件名
//(新版本可以是库类型string 也可以时C风格字符数组(老只允许这个))
ofstream out; // 输出文件流 未关联到 任何文件

3、用 fstream 代替 iostream&:在要求使用 基类型对象的地方,可以 使用继承类型的对象 来替代。接受一个 iostream类型引用(或指针)参数的函数,用一个对应的 fstream(或 sstream)类型来调用
也就是,有一个函数 接受一个 ostream& 参数,在调用这个函数时,可以传递给它一个 ofstream 对象,对 istream& 和 ifstream 也是类似

ifstream input(argv[1]); // 注意
ofstream output(argv[2]); // 注意
Sales_data total; // 保存销售总额变量
if (read(input, total)) { // 注意Sales_data trans; // 保存下一条销售记录的变量while (read(input, trans)) { // 注意 读取剩余记录if (total.isbn() == trans.isbn())total.combine(trans); // 更新销售总额else {print(output, total) << endl; // 注意total = trans; // 处理下一本书}}print(output, total) << endl; // 注意
}
elsecerr << "No data" << endl;

注意 对read和print的调用。虽然 两个函数定义时 指定的形参分别是 istream& 和 ostream&,但可以 向它们传递 fstream对象

4、成员函数 open和close:定义一个 空文件流对象,随后调用open将 它与文件关联起来:

ifstream in(ifile); // 构筑一个 ifstream 并打开给定文件
ofstream out; // 输出文件流 未与任何文件关联
out.open(ifile + ".copy"); // 打开指定文件

如果 调用open失败,failbit 会被置位
所以 进行open是否成功的检测 是一个好习惯:

if (out) // 检查open是否成功,如果open成功,就可以使用文件了

一旦 一个文件流已经打开,它就保持 与对应文件的关联。对一个 已经打开的文件流 调用open会失败,导致 failbit被置位。随后的 试图使用文件的操作 都会失败。为了 将文件流关联到 另外一个文件,必须首先 关闭已经关联的文件

in.close(); // 关闭文件
in.open(ifile + "2"); // 打开另一个文件

如果open成功,open会设置 流的状态,使得 good() 为 true

5、自动构造 和 析构:main函数接受一个 要处理的文件列表

// 对每个传递给程序的文件 执行循环操作
for (auto p = argv + 1; p != argv + argc; ++p) {ifstream input(*p); // 创建输入流 并打开文件if (input) { // 文件打开成功,处理此文件process(input);}else // 如果open失败,打印一条错误信息 并继续处理下一个文件cerr << "couldn't open:" + string(*p);
} // 每个循环步input都会离开作用域,因此会被销毁

当一个 fstream对象 离开其作用域时,与之关联的文件 会自动关闭
当一个fstream对象被销毁时,close会被 自动调用

6、编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将 每一行 作为一个独立的元素 存于vector中(ifstream getline 使用)

#include <iostream>
#include <fstream>
#include <string>
#include <vector>using namespace std;int main()
{ifstream ifs("data.txt");vector<string> str;if (ifs) {string tmp;while (getline(ifs, tmp)) {str.push_back(tmp);}}for (const auto& i : str) {cout << i << endl;}return 0;
}

重写上面的程序,将 每个单词 作为一个独立的元素 进行存储

#include <iostream>
#include <fstream>
#include <string>
#include <vector>using namespace std;int main()
{ifstream ifs("data.txt");vector<string> str;if (ifs) {string tmp;while (ifs >> tmp) { // 跟上面代码不同的地方str.push_back(tmp);}}for (const auto& i : str) {cout << i << endl;}return 0;
}

7、从一个文件中读取交易记录。将文件名作为一个参数传递给main
8.6.cpp

#include <fstream>
#include <iostream>
#include <string>
#include "D:/VisualStudio/WorkSpace/Primer7/Primer7/Sales_data_21.h" // 引之前的头文件using namespace std;int main(int argc, char* argv[]) {ifstream ifs(argv[1]); // 第0个是程序名if (!ifs) return 1;// 文件内容读取成功Sales_data s(ifs);if (!s.isbn().empty()) {Sales_data cur;while (read(ifs, cur)) {if (cur.isbn() == s.isbn()) {s.combine(cur);}else {print(cout, s);cout << endl;s = cur;}}print(cout, s);cout << endl;}else {std::cerr << "No data" << std::endl;return -1;}return 0;
}

data_6.txt

123 10 10
123 12 12
12 10 10

运行结果:
运行结果

2.2 文件模式

1、每个流 都有一个关联的文件模式,用来 指出如何使用文件

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

截断文件:即文件中原有的内容被删除,并将文件大小截断为零
当以 trunc 模式打开一个文件时,如果该文件已经存在,则会清空文件中的内容,将文件大小截断为零字节;如果文件不存在,则会创建一个空文件

无论用哪种方式 打开文件,都可以指定文件模式,调用open打开文件时可以,用一个文件名 初始化流 来隐式打开文件时 也可以

2、指定文件模式 有如下限制:
1)只可以对 ofstream 或 fstream 对象设定out模式
2)只可以对 ifstream 或 fstream 对象设定in模式
3)只有当 out 也被设定时 才可设定trunc模式
4)只要 trunc没被设定,就可以设定app模式。在app模式下,即使 没有显式指定out模式,文件也总以 输出方式被打开
5)默认情况下,即使 没有设定trunc,以 out模式打开的文件 也会被截断。为了保留以out模式 打开的文件的内容,必须同时指定app模式,这样只会将 数据追加读写到 文件末尾;或者 同时指定in模式,即打开文件 同时进行读写操作
6)ate和binary模式 可用于任何类型的文件流对象,且可以 与其他任何文件模式 组合使用

3、每个文件流类型 都定义了 一个默认的文件模式:
与ifstream关联的文件 默认以in模式打开;
与ofstream关联的文件 默认以out模式打开;
与fstream关联的文件 默认以in和out模式打开

4、以out模式 打开文件会丢弃。阻止一个ofstream 清空给定文件内容的方法 是同时指定app模式:

// 在这几条语句中,file1都被截断
ofstream out("file1"); // 隐含以输出模式 打开文件并截断文件
ofstream out2("file1", ofstream::out); // 隐含地截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc);// 为了保留文件内容,必须显式地指定app
ofstream app("file2", ofstream::app); // 隐含为输出模式
ofstream app2("file2", ofstream::out | ofstream::app);

关于 ofstream out3("file1", ofstream::out | ofstream::trunc);
1)“file1”:这是要打开或创建的文件的名称。如果文件不存在,则会创建一个新文件;如果文件已经存在,则会截断文件(清空文件内容)
2)ofstream::out | ofstream::trunc:这是打开文件时指定的模式。ofstream::out 表示以输出模式打开文件,允许写入数据到文件中。ofstream::trunc 表示截断模式,如果文件已存在,则会清空文件内容;如果文件不存在,则会创建一个新的空文件。使用位运算符 | 将这两个模式组合在一起,表示同时使用这两个模式打开文件

这行代码的意思是以输出模式打开文件 “file1”,如果文件已存在,则清空文件内容;如果文件不存在,则创建一个新的空文件,并将文件输出流对象 out3 与该文件关联

|(按位或):两个相应的二进制位只要有一个为 1 时,结果为 1,否则为 0

5、每次调用open时 都会确定文件模式:对于一个给定流,每当打开文件时,都可以 改变其文件模式

ofstream out; // 未指定文件打开模式
out.open("data.txt"); // 模式隐含设置为 输出和截断
out.close(); // 关闭out,以便将其用于其他文件
out.open("data2.txt", ofstream::app); // 模式为 输出和追加
out.close();

第一个open调用 未显式指定输出模式,文件隐式地 以out模式打开。out模式 意味着 同时使用trunc模式。当前目录下 名为data.txt的文件将被清空。当打开名为 data2.txt的文件时,指定了append模式,文件中 已有的数据都得以保留,所有写操作 都在文件末尾进行

6、修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给main函数
8.7.cpp

#include <iostream>
#include <string>
#include <fstream>
#include "Sales_data.h"int main(int argc, char** argv)
{std::ifstream ifs(argv[1]);std::ofstream ofs(argv[2]); // 打开ofstream才会清空,同一个ofstream使用多次是不会的,多次运行程序才会if (!ifs) return 1;Sales_data total(ifs);if (!total.isbn().empty()){Sales_data trans;while (read(ifs, trans)){if (total.isbn() == trans.isbn()){total.combine(trans);}else{print(ofs, total);ofs << std::endl;total = trans;}}print(ofs, total);ofs << std::endl;return 0;}else{std::cerr << "No data?!" << std::endl;return -1;  }
}

修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留
8.8.cpp

#include <iostream>
#include <string>
#include <fstream>
#include "Sales_data.h"int main(int argc, char** argv)
{std::ifstream ifs(argv[1]);std::ofstream ofs(argv[2], std::ofstream::app); // 只需要显式指定app模式即可,其他都是一样的if (!ifs) return 1;Sales_data total(ifs);if (!total.isbn().empty()){Sales_data trans;while (read(ifs, trans)){if (total.isbn() == trans.isbn()){total.combine(trans);}else{print(ofs, total);ofs << std::endl;total = trans;}}print(ofs, total);ofs << std::endl;return 0;}else{std::cerr << "No data?!" << std::endl;return -1;  }
}

运行结果:
运行结果

3、string流

1、sstream 头文件定义了 三个类型支持内存IO,可以向string写入数据,从string读取数据,就像string是一个IO流一样
istringstream 从string读取数据,ostringstream 向string写入数据,而 头文件stringstream 既可从 string读取数据 也可向string写数据
与 fstream类型类似,头文件sstream中定义的类型 都继承自 iostream头文件中定义的类型。除了继承得来的操作,sstream中定义的类型 还增加了一些成员 来管理与流相关联的 string

2、stringstream特有的操作,不能对其他IO类型调用这些操作

对象或函数解释
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

3.1 使用 istringstream

1、处理行内的单个单词时,通常可以使用 istringstream

例:用getline从标准输入 读取整条记录。如果 getline 调用成功,line中将保存着 从输入文件而来的一条记录
while循环中,从一个string而不是 标准输入 读取数据。当string中的数据 全部读出后,会触发“文件结束”信号,在record上的 下一个输入操作会失败

#include <string>
#include <vector>
#include <iostream>
#include <sstream>using namespace std;struct PersonInfo{string name;vector<string> phones;
};int main() 
{string line, word; // 分别保存来自输入的一行 和 单词vector<PersonInfo> people; // 保存来自输入的所有记录// 逐行从输入读取数据,直至cin遇到文件尾while (getline(cin, line)) {PersonInfo info; // 创建一个保存此纪录数据的对象istringstream record(line); // 将记录绑定到 刚读入的行// 这样就可以在此 istringstream 上使用输入运算符来读取 当前记录中的每个元素record >> info.name;while (record >> word) {info.phones.push_back(word);}people.push_back(info); // 记录这个记录}for (const PersonInfo& e : people) {cout << e.name << endl;for (const string& s : e.phones) {cout << s << endl;}cout << endl;}return 0;
}

运行结果:
运行结果

2、编写函数打印一个istringstream对象的内容(+异常处理)

以下情况下 in.eof() 会为 true:
1)从文件中读取:当读取到文件末尾时,in.eof()会返回 true
2)从字符串流中读取:当读取到字符串流的结尾时,in.eof()会返回true

逗号操作符。它允许在单个语句中执行多个操作,并返回最后一个操作的结果

in.ignore(100, ‘\0’);// 忽略输入流中的字符,直到达到以下条件之一:已经忽略了100个字符;遇到了终止字符’\0’

#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>std::istream& f(std::istream& in) {std::string v;while (in >> v, !in.eof()) {// 逗号操作符。它允许在单个语句中执行多个操作,并返回最后一个操作的结果std::cout << v << std::endl;// 当字符串只有一个空格的时候不会进入这个循环}std::cout << v << std::endl;if (in.bad())// 在底层 IO 操作失败时返回 true,例如文件无法打开、设备错误、磁盘故障等throw std::runtime_error("IO 流错误");if (in.fail()) { // 会在输入操作失败时(类型不匹配,文件结束或无效数据,IO错误)返回truestd::cerr << "数据错误,请重试" << std::endl;in.clear();// 清除流的状态标志in.ignore(100, '\0');// 忽略输入流中的字符,直到达到以下条件之一:已经忽略了100个字符;遇到了终止字符'\0'return in;}in.clear();return in;
}int main() {std::istringstream in(" ");// 空会导致输入操作失败,输出 数据错误f(in);std::istringstream in2("C++ Primer 第五版");// 每碰到一个空格就进行切片sf(in2);return 0;
}

运行结果:
运行结果
如果record 对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将record的定义移到while 循环之外

1)record.str(line) 是将字符串 line 赋值给 istringstream 对象 record 的成员变量 str
在这里,record 是一个 istringstream 对象,它可以用于从字符串中提取数据,而 line 是从标准输入读取的一行字符串。
通过 record.str(line),将 line 中的内容设置到 record 中,这样就可以像从输入流中读取数据一样,通过 record 逐个读取单词。这在代码中的作用是将每一行字符串分解为单词,以便后续处理

2)record.clear() 的目的是在每次循环迭代开始时清除 record 的状态,以便它可以重新读取新的一行输入数据
因为在每次循环迭代中,我们使用 record.str(line) 将 line 中的内容设置到 record 中,但同时 record 对象保留了之前的状态,包括可能的错误标志或文件结束标志
因此,在每次循环开始时,我们调用 record.clear() 来清除这些状态,以确保 record 对象在新的一行数据读取之前处于一个良好的状态

3)在C++中,istringstream 类具有两个主要的构造函数,它们的作用有些不同(record.str(line) 和 record(line) 区别):
1、record.str(line):这是调用 istringstream 对象的成员函数 str(),它的作用是将给定的字符串 line 赋值给 istringstream 对象 record。这个函数主要用于将一个已有的字符串赋值给 istringstream 对象,使得后续可以通过该对象读取该字符串中的内容
2、record(line):这是调用 istringstream 类的构造函数,它的作用是创建一个 istringstream 对象,并将给定的字符串 line 传递给该构造函数,从而初始化 istringstream 对象的内部状态,使得后续可以通过该对象读取该字符串中的内容

因此,两者的功能都是将一个字符串传递给 istringstream 对象,但是前者是通过成员函数来实现,而后者是通过构造函数来实现。在本程序中,两者的效果是相同的,都将 line 中的内容设置到 record 中

#include <string>
#include <vector>
#include <iostream>
#include <sstream>using namespace std;struct PersonInfo {string name;vector<string> phones;
};int main()
{string line, word;vector<PersonInfo> people;istringstream record; // 修改while (getline(cin, line)) {PersonInfo info;record.str(line); // 修改record >> info.name;while (record >> word) {info.phones.push_back(word);}people.push_back(info);record.clear(); // 重复使用字符串流时,每次都要调用 clear}for (const PersonInfo& e : people) {cout << e.name << endl;for (const string& s : e.phones) {cout << s << endl;}cout << endl;}return 0;
}

运行结果:
在这里插入图片描述

为什么没有在PersonInfo中使用类内初始化
由于每个人的电话号码数量不固定,因此更好的方式不是通过类内初始化指定人名和所有电话号码,而是在缺省初始化之后,在程序中设置人名并逐个添加电话号码

3、编写程序,将来自一个文件中的行保存在一个vector中。然后使用一个istringstream从vector读取数据元素,每次读取一个单词

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>using namespace std;int main()
{ifstream ifs("data_10.txt");if (!ifs) // 注意对异常的处理{cerr << "open error" << endl;return -1;}vector<string> vs;string line;while (getline(ifs, line)) { // 从文件按行读取,读到line里面vs.push_back(line);cout << line << endl;}cout << endl;for (const string &s : vs) {istringstream iss(s);// 把vs存储的每一行每次跟iss绑定,用iss完成对s的读取string ss;while (iss >> ss) // 每次读取到的字符串(按空格分割)存在ss里面cout << ss << endl;}return 0;
}

data_10.txt

ashergu is a good engineer
ashergu is a good boy
ashergu 1234

运行结果:
运行结果
关于字符串中有个空格:

#include <iostream>
#include <sstream>
#include <string>int main() {std::istringstream iss(" "); // 输入流只包含一个空格字符std::string str;if (iss >> str) // 为false,不会进入循环std::cout << "true" << std::endl;if (str.empty()) {std::cout << "输入为空" << std::endl; // 输出为空,没有东西读进去}else {std::cout << "输入为: " << str << std::endl;}return 0;
}

运行结果:
运行结果

3.2 使用 ostringstream

1、当逐步构造输出,希望最后一起打印时,ostringstream 是有用的

例:上一节的例子,想逐个 验证电话号码 并改变其格式。如果 所有号码都是有效的人,输出一个新的文件,包含 改变格式后的号码;对于有无效号码的人,打印一条包含人名 和 无效号码的错误信息

因为 不希望输出有无效号码的人,所以 对每个人,直至 验证完所有的号码后 才能进行输出操作
先将 输出内容“写入”到一个内存 ostringstream 中
有两个函数,valid() 和 format(),分别完成 电话号码的验证 和 改变格式功能。“写入”操作 实际上转换为 string操作,分别向 formatted 和 badNums 中的string对象 添加字符

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>using namespace std;struct PersonInfo{string name;vector<string> phones;
};string format(string& s) {return s;
}bool valid(const string& s) {for (const char c : s) {if (!isdigit(c)) // 每个字符都需要是数字return false;}return true;
}int main()
{ifstream ifs("data_13.txt");if (!ifs)cerr << "data load error" << endl;string line;vector<PersonInfo> people;vector<string> record;while (getline(ifs, line)) { // 一行一行读入record.push_back(line);}for (const string& s : record) {PersonInfo person;istringstream iss(s);// 对每一行进行切片iss >> person.name;string ss;while (iss >> ss) {person.phones.push_back(ss);}people.push_back(person); // 记录每一个人的信息}for (const auto& p : people) {ostringstream formatted, badNums; // 拆成两个ostringstream分别记录合法 / 非法号码for (const auto& s : p.phones) {if (!valid(s)) {badNums << s << " ";}else {formatted << s << " ";}}if (badNums.str().empty()) { // 没有非法的号码cout << p.name << " " << formatted.str() << endl;}else {cerr << "input error: " << p.name << " " << badNums.str() << endl;}}return 0;
}

data_13.txt

xiaoyi 13265606 51315 5315366
xiaoer 6843612 83514883 4dhjjk84513
xiaosan 513653 35135

运行结果:
运行结果

术语表

1、条件状态:可被 任何流类型 使用的一组标志和函数,用来 指出给定流是否可用

2、istringstream:用来 从给定string读取 数据的字符串流

3、字符串流:用于读写 string的流对象。除了普通的iostream操作外,字符串流 还定义了一个名为str的重载成员
调用 str的无参版本 会返回字符串流关联的 string。调用时 传递给它 一个string参数,则会将字符串流 与 该string的一个拷贝关联

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2815288.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Netty入门指南:从零开始的异步网络通信

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Netty入门指南&#xff1a;从零开始的异步网络通信 前言Netty简介由来&#xff1a;发展历程&#xff1a;异步、事件驱动的编程模型&#xff1a; 核心组件解析通信协议高性能特性异步编程范式性能优化与…

Linux零基础快速入门

Linux的诞生 Linux创始人:林纳斯 托瓦兹 Linux 诞生于1991年&#xff0c;作者上大学期间 因为创始人在上大学期间经常需要浏览新闻和处理邮件&#xff0c;发现现有的操作系统不好用,于是他决心自己写一个保护模式下的操作系统&#xff0c;这就是Linux的原型&#xff0c;当时他…

【MySQL】DCL

DCL英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访问权限。 1. 管理用户 在MySQL数据库中&#xff0c;DCL&#xff08;数据控制语言&#xff09;是用来管理用户和权限的语句集合。通过DCL语句&#xff0c;可以创建、修改、删…

leetcode 重复的子字符串

前要推理 以abababab为例&#xff0c;这里最主要的就是根据相等前后缀进行推导 s [ 0123 ] 如 t【 0123 】 f 【01 23 】 后两个分别是前后缀&#xff0c;第一个是总的字符串&#xff0c;然后可以推导 //首先还是算出…

Spring的定时任务不生效、不触发,一些可能的原因,和具体的解决方法。

1 . 未在启动类上加 EnableScheduling 注解 原因&#xff1a;未在Spring Boot应用主类上添加EnableScheduling注解或未在XML配置文件中配置定时任务的启用。解决方法&#xff1a;确保在应用的配置类上添加EnableScheduling注解&#xff0c;启用定时任务。 2 . cron 表达式书写…

P4859 已经没有什么好害怕的了(二项式反演+dp)

题录&#xff1a;P4859 已经没有什么好害怕的了 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路: 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<cstring> #include<cmath> #include<ctim…

Kubernetes剖析

Kubernetes剖析 前言 ​ 容器技术这样一个新生事物&#xff0c;完全重塑了整个云计算市场的形态。它不仅催生出了一批年轻有为的容器技术人&#xff0c;更培育出了一个具有相当规模的开源基础设施技术市场。 ​ 在这个市场里&#xff0c;不仅有 Google、Microsoft 等技术巨擘…

【GPTs分享】GPTs分享之Photo Multiverse,不唱、跳、RAP的坤坤能叫坤坤吗

今日GPTs分享&#xff1a;Photo Multiverse。Photo Multiverse 是一款基于先进人工智能技术的创意辅助工具&#xff0c;专为艺术家和创意人士设计&#xff0c;以探索和创造多元宇宙中的视觉艺术。它通过结合古老的艺术神秘主义和现代技术&#xff0c;帮助用户将他们的想象力变为…

linux gdb 调试工具

1.写程序 首先&#xff0c;我们先写出一个 .c 或者.cpp程序 如 然后 gcc -g hello.c -o hello 或者 g -g hello.cpp -o hello &#xff08;-g&#xff09;要加 2. gdb调试 用 gdb &#xff08;可执行程序&#xff0c;如hello&#xff09; 进入之后&#xff0c;有…

el-table 多选表格存在分页,编辑再次操作勾选会丢失原来选中的数据

el-table表格多选时&#xff0c;只需要添加type"selection"&#xff0c; row-key及selection-change&#xff0c;如果存在分页时需要加上reserve-selection&#xff0c;这里就不写具体的实现方法了&#xff0c;可以查看我之前的文章&#xff0c;这篇文章主要说一下存…

算法打卡day5|哈希表篇01|Leetcode 242.有效的字母异位词 、19.删除链表的倒数第N个节点、202. 快乐数、1. 两数之和

哈希表基础知识 哈希表 哈希表关键码就是数组的索引下标&#xff0c;然后通过下标直接访问数组中的元素&#xff1b;数组就是哈希表的一种 一般哈希表都是用来快速判断一个元素是否出现集合里。例如要查询一个名字是否在班级里&#xff1a; 要枚举的话时间复杂度是O(n)&…

Linux服务器中文乱码如何解决

如果服务器上数字和英文均可正常展示&#xff0c;只有中文是奇奇怪怪的乱码&#xff0c;那么可以考虑是服务器本身字体输出有问题。 如何在服务器上安装中文宋体字体库呢&#xff0c;排查及安装字体库步骤如下&#xff1a; 使用 fc-list命令检查服务器是否安装字体库如果提示…

Docker部署Portainer图形化管理工具

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 Portainer 是一个轻量级的容器管理工具&#xff0c;可以通过 Web 界面对 Docker 容器进行管理和监控。它提供了可…

模仿蜘蛛工作原理 苏黎世联邦理工学院研发牛油果机器人可在雨林树冠穿行

对于野外环境生物监测的研究人员来讲&#xff0c;收集生物多样性数据已成为日常工作重要组成部分&#xff0c;特别是对于热带雨林的茂密树冠当中活跃着非常多的动物、昆虫与植物。每次勘察都需要研究人员爬上茂密树冠收集数据&#xff0c;一方面增加了数据收集难度&#xff0c;…

性能测试-反编译jar

方法一&#xff0c;使用jd-gui 1、官网下载&#xff1a;Java Decompiler 2、下载mac版本后&#xff0c;解压&#xff0c;如下所示&#xff1a; 双击 JD_GUI&#xff0c;提示错误&#xff0c;如下所示&#xff1a; 已经安装了java 17&#xff0c;是java 1.8以上版本&#xff0…

milvus upsert流程源码分析

milvus版本:v2.3.2 整体架构: Upsert 的数据流向: 1.客户端sdk发出Upsert API请求。 import numpy as np from pymilvus import (connections,Collection, )num_entities, dim 4, 3print("start connecting to Milvus") connections.connect("default",…

程序员年龄增大后的职业出路是什么?

​新年刚过&#xff0c;返工在即。程序员们都收到大大小小的开门红&#xff0c;开启今年新征程。但是有人欢喜有人忧…… 本想着2024年Android行业会好过一些&#xff0c;还是避免不了裁员风险。在安卓历经了10多年的发展后&#xff0c;因为头部公司的稳定和相互的竞争情况下&a…

猜数字游戏,炸弹数,random

package com.zhang.random;import java.util.Random; import java.util.Scanner;public class RandomTest2 {public static void main(String[] args) {//随机生成一个1~100之间的数&#xff0c;提示用户猜测&#xff0c;猜大提示猜大&#xff0c;猜小提示小了&#xff0c;直到…

LeetCode刷题——144. 二叉树的前序遍历

热身题&#xff1a;二叉树的前序遍历 题目描述&#xff1a; 题目链接&#xff1a;https://leetcode.cn/problems/binary-tree-preorder-traversal/description/ 递归解法&#xff1a; 关键点1 递归三部曲&#xff1a; 第一步&#xff1a;确定递归函数的返回值和参数列表 第…

11.vue学习笔记(组件生命周期+生命周期应用+动态组件+组件保持存活)

文章目录 1.组件生命周期2.生命周期应用2.1通过ref获取元素DOM结构2.2.模拟网络请求渲染数据 3.动态组件3.1.A&#xff0c;B两个组件 4.组件保持存活&#xff08;销毁期&#xff09; 1.组件生命周期 每个Vue组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置…