《C++Primer 第五版》——第八章 IO 库

Error: dependent ‘..\..\..\..\Qt\5.15.2\msvc2019\include\QtWidgets\QMainWindow‘ does not exist. 核心问题:修改 Build 目录为源码子目录后,MSVC 编译报错,本质是变量设置异常,导致 qmake 生成多余回溯路径;临时解决:在 .pro 文件添加;最优解决:将上述配置写入 Qt 的 msvc 对应 qmake.conf 全局文件,一劳永逸;禁止 qmake 计算路径时向上回溯,避免生成异常路径。 阅读详情


C++语言不直接处理输入输出,而是通过一组定义在标准库中的class类型,即 流类类型(stream class type) 来处理IO操作。这些类支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等,还有一些类允许内存IO,即,从内存读取数据,向内存写入数据。

IO 库定义了读写内置类型值的操作。除此之外,一些类,比如 string ,通常也会定义类似的IO操作,来读写class类型自己的对象。

  • istream 输入流类型,提供输入操作。
  • ostream 输出流类型,提供输出操作。
  • cin 一个 istream 对象,从标准输入读取数据。
  • cout 一个 ostream 对象,向标准输出写入数据。
  • cerr 一个 ostream 对象,用户输出程序错误消息,写入到标准错误。
  • >> 运算符,用来从一个 istream 对象读取输入数据。
  • << 运算符,用来从一个 ostream 对象写入输出数据。
  • getline 函数,从一个给定的 istream 读取一行数据,存入一个给定的 string 对象中。

8.1 IO 类

实际上,我们可以实现对控制台窗口、文件内容、string 对象中的字符进行 IO 操作。此外,应用程序可能读写需要宽字符支持的语言 (2.1.1中提及)。

为了支持不同类型的IO操作,在 istream 和 ostream 之外,标准库还定义了其他一些 IO 类型,比如:

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

其中头文件 iostream 定义了用于读写流的基本类型;fstream 定义了读写命名文件的类型;sstream 定义了读写内存 string 对象的类型。

同时,这些 IO 类型还分为两个版本,除了常规版本外,还有一个专门处理宽字符的版本,这个版本的class类型和函数的名字以一个 w 开头。例如wcin、wcout和wcerr是分别对应cin、cout和cerr的宽字符版对象。宽字符版本的类型和对象与其对应的普通 char 版本的类型定义在同一个头文件中,例如,头文件 fstream 定义了 ifstream 和 wifstream 类型。

IO 类型间的关系

概念上,设备类型和字符大小都不会影响到要执行的 IO 操作。例如,我们可以用 >> 读取数据,而不用管是从一个控制台窗口,一个磁盘文件,还是一个 string 对象中读取数据。类似的,也不用管读取的字符能存入一个 char 变量内,还是需要一个 wchar_t 类型对象。

标准库使得程序员可以无视不同类型的流之间的差异,这是通过 继承机制(inheritance) 实现的。

利用模板,程序员可以使用具有继承关系的class类型,而不必了解继承机制工作的细节(后面将在第15章和18.3节介绍C++如何支持继承机制)。

在这里插入图片描述

简单说,继承机制使程序员可以声明一个特定的类继承自另一个类。我们通常可以将一个派生类(子类,又叫继承类)对象当作基类(父类,又叫所继承的类)对象来使用,这是很重要的一个事情。

举个简单的例子,类型 ifstream 和 istringstream 都继承自 istream 。因此,可以像使用 istream 对象一样来使用 ifstream 和 istringstream 对象。换句话说,可以像使用 cin 一样使用这些类型的对象。比如,可以对一个 ifstream 或 istringstream 对象调用 getline 函数,也可以使用 >> 运算符从一个 ifstream 或 istringstream 对象中读取数据。

Note:

下面介绍的标准库特性都可以无差别应用于普通流、文件流和 string 流及 char 或宽字符流版本。

8.1.1 IO 对象无拷贝或赋值

如下所示,对于标准库的 IO 类型,无法拷贝 IO 对象或对 IO 对象赋值:

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

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

8.1.2 条件状态

IO 操作可能会发生错误,其中一些是可恢复的,而其他错误则发生在系统内部,超出了应用程序可修正的范围。
下面的表格列出了 IO 类定义了一些函数和标志,它帮助用来访问和操作流的条件状态 (condition state)。

函数或标志IO 库条件状态的说明
strm::iostatestrm 是一个标识符,它可以是标准库中列出的任意一种 IO 类型。iostate是一种依赖机器的整型,代表流的条件状态。
strm::badbitstrm::iostate 值,用来指示流已崩溃
strm::failbitstrm::iostate 值,用来指示一个 IO 操作失败。
strm::eofbitstrm::iostate 值,用来指示流到达文件结束。
strm::gootbitstrm::iostate 值,用来指示流不处于错误状态。该值保证为零。
s.eof()s 是一个标识符,表示标准库中列出的任意一种 IO 类型的对象,比如 cin 和 cout 。
若流 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 。

下面是一个简单的 IO 错误例子:

int ival;
cin >> ival;

如果我们在标准输入上用键盘输入 Boo ,读操作就会失败。因为代码中的输入运算符期待读取一个 int 类型的值,但却先得到了一个字符 B 。这样, IO 对象 cin 会进入错误状态。类似的,如果我们输入一个文件结束标识,cin 也会进入错误状态。

一个流一旦发生错误,其后续的 IO 操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用:

while (cin >> word)
	// ok: 读取操作成功
if(cin);

While 循环检查 >> 表达式返回的流的状态。如果输入操作成功,流保持有效状态,则条件为 true 。

查询流的状态

将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们也需要知道流为什么失败。 例如,在键入文件结束标识后我们的应对措施,可能与遇到一个 IO 设备错误的处理方式是不同的。

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

关于流的状态标志

①badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位(即代表badbit的那一位设置为1),流就无法再使用了。
②在发生可恢复错误后, failbit 被置位。如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。
③如果到达文件结束位置, eofbit 和 failbit 都会被置位。
④goodbit 的值为 0 (即没有一位发生置位),表示流未发生错误。
so:
如果 badbit , failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败。

标准库还定义了一组成员函数来查询这些标志位的状态。 good() 成员函数在所有错误位均未置位的情况下返回 true,而 bad() 、 fail() 和 eof() 则在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail() 也会返回 true。这意味着,使用 good() 或 fail() 是确定流的总体状态的正确方法。

管理条件状态

标准库为 IO 类型定义了一些成员函数。下面用 strm 来指代特定的 IO 类型。

strm 类型的流对象的 rdstate() 成员函数返回一个 strm::iostate 类型的值,对应流的当前状态。
setstate(flags) 成员函数将对象的指定条件位置位(即 flags ,这是一个 strm::iostate 类型的值),表示发生了其对应的错误。
clear() 是一个重载的成员函数:它有一个不接受参数的版本,而另一个版本接受一个 strm::iostate 类型的参数。

其中不接受参数的版本会清除所有错误标志位(即复位全部位)。执行 clear() 成员函数后,调用 good() 会返回 true 。我们可以这样使用这些成员:

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

带参数的版本接受一个 strm::iostate 类型的值,表示流的新状态。为了复位单一的条件状态位,我们首先用 rdstate() 成员函数读出流对象的当前条件状态,然后用位操作将所需位复位来生成新的状态。
例如,下面的代码将 failbit 和 badbit 复位,但保持 eofbit 不变:

// 复位 failbit 和 badbit, 其他位置保持不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

8.1.3 管理输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如,如果执行下面代码:

cout << "please enter a value..";

文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升——即将多次写操作合并为一次写操作,减少了写操作的次数。

输入/输出操纵符(Input/output manipulators)

标准库定义了一组输入/输出操纵符来控制该流的格式状态。

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

  • 程序正常结束,作为 main() 函数的 return 操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
  • 我们可以使用操纵符如 endl 来显式刷新缓冲区。
  • 在每个输出操作之后,我们可以用操作符 unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对关联到标准错误的一个 iostream 对象,即 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下, cin 和 cerr 都关联到 cout 。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新。

刷新输出缓冲区

我们已经使用过操纵符 endl ,它负责完成换行并刷新缓冲区的工作。IO 库中还有两个类似的操纵符: flush 和 ends 。 flush 刷新缓冲区,但不输出任何额外的字符。而 ends 向缓冲区插入一个空字符,然后刷新缓冲区。
值得一提的是,cout 所属 ostream 类中还提供有 flush() 成员函数,它和 flush 操纵符的功能完全一样,仅在使用方法上( 即cout.flush() )有区别。

cout << "hi" << endl;	// 输出hi和一个换行,然后刷新缓冲区
cout << "hi" << flush;	// 输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi" << ends;  // 输出hi和一个空字符,然后刷新缓冲区

unitbuf 操纵符

如果想在每次输出操作后都刷新缓冲区,我们可以使用 unitbuf 操纵符。unitbuf告诉流在接下来的每次写操作之后都进行一次 flush 操作。而 nounitbuf 操纵符则重置流, 使其恢复使用正常的系统管理的缓冲区刷新机制。

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

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

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

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。 标准库将 cout 和 cin 关联在一起,因此下面语句导致 cout 的缓冲区被刷新:

cin >> ival;

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

tie() 成员函数可以用来为流对象绑定输出流,它有两个重载的版本:
第一个版本不带参数,返冋指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个输出流的指针,如果该对象未关联到流,则返回空指针。
第二个版本接受一个指向输出流的指针,将本对象关联到此输出流,比如 x.tie(&o) 将流 x 关联到输出流 o。

tie成员函数的函数声明如下(适用于任何一个输出流类型):

//返回指向绑定的输出流的指针。
basic_ostream<char_type,traits_type>* tie() const;
//将 os 指向的输出流绑定的该对象上,并返回上一个绑定的输出流指针。
basic_ostream<char_type,traits_type>* tie(basic_ostream<char_type,traits_type>* tiestr);

既可以将一个输入流对象关联到另一个输出流,也可以将一个输出流关联到另一个输出流:

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

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


8.2 文件输入输出

头文件 fstream 定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。在17.5.3节中将介绍如何对同一个文件流既读又写。

这些类型提供的操作与之前已经使用过的对象cin和cout的操作一样。特别是可以用IO运算符(<<和>>)来读写文件,可以用getline函数从一个ifstream读取数据。也就是说,包括8.1节中介绍的操作也都适用于这些类型。

除了继承自iostream类型的行为之外,头文件 fstream 中定义的类型还增加了一些新的成员来管理与流关联的文件。在下面列出了这些操作,我们可以对fstream,ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。

头文件 fstream 中定义的这些类型的单参数构造函数都都是 explicit 的,即不能通过隐式转换参数。

头文件 fstream 中定义的类型的特有操作操作详情
fs fstrm;创建一个未绑定的文件流。 fs 是定义在 fstream 头文件的一个类型。
fs fstrm(s);创建一个 fs 对象,并打开名为 s 的文件。 s 可以是 string 类型或C风格字符串指针。
头文件 fstream 中定义的这些类型的构造函数都是 explicit 的。 默认文件的打开模式 mode 取决于是什么类型。
fs fstrm(s,mode);与前一个构造函数类似,但是按指定模式mode打开文件。其中mode是定义在头文件 fstream 中的一系列标识符。
fstrm.open(s)打开名为 s 的文件,并将文件与对象绑定。 默认文件的打开模式 mode 取决于是什么类型。 s 可以是 string 类型或C风格字符串指针。返回 void。
fstrm.open(s,mode)以给定的 mode 打开 s,并并将文件与对象绑定。
fstrm.close()关闭与该对象绑定的文件。返回 void。
fstrm.is_open()返回一个 bool 值,指示与该对象关联的文件是否成功打开且尚未关闭。

8.2.1 使用文件流对象

当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流类都定义了名为 open 和 close 的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。

创建文件流对象时,我们可以提供文件名(可选的)。如果提供了一个文件名,则 open 成员函数会自动被调用:

ifstream in(ifile);	// 构造一个 ifstream 对象,打开给定文件并将双方绑定
ofstream out;		// 输出文件流out未关联到任何文件

这段代码定义了一个输入流 in ,它被初始化为从文件读取数据,文件名由参数 ifile 指定。第二条语句定义了一个输出流out,未与任何文件关联。在新C++标准中,文件名既可以是库类型string对象,也可以是C风格字符数组(参见3.5.4节)。旧版本的标准库只允许C风格字符数组。

用 fstream 代替 iostream&

在8.1节已经提到过,在要求使用父类对象的地方,我们可以用子类对象来替代。

这意味着,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream或sstream类型的复合类型来调用。也就是说,如果有一个函数接受一个ostream&参数,我们在调用这个函数时,可以传递给它一个ofstream对象,对istream&和ifstream也是类似的。

成员函数 open和 close

如果我们定义了一个空文件流对象(即未绑定文件的文件流对象),可以随后调用 open 成员函数来将它与文件关联起来:

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

如果调用open失败, failbit 会被置位。 因为调用open可能失败,进行open是否成功的检测通常是一个好习惯:

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

这个条件判断与我们之前将cin用作条件相似。如果open失败,条件会为假,我们就不会去使用out了。

一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用open会失败,并会导致 failbit 被置位。而随后的试图使用该文件流的操作自然都会失败。 为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,我们可以打开新的文件:

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

如果open成功,则open会设置流的状态,使得其调用good()返回 true 。

自动构造和析构

考虑这样一个程序,它的main函数接受一个要处理的文件列表(参见6.2.5节 mian:处理命令行选项 )。这种程序可能会有如下的循环:

//对每个传递给程序的文件执行循环操作
for (auto p = argv + 1; p < argv + argc; ++p) {
	std::ifstream input(*p); //创建输出流对象input并打开文件p
	if (input){              //如果文件打开成功,“处理”此文件
		process(input);  
	} else{
		std::cerr << "couldn`t open: " + std::string(*p);
	}
}//每个循环步input都会离开作用域,因此会被销毁再在下一次循环中被创建

每个循环步构造一个新的名为input的ifstream对象,并打开它来读取给定的文件。像之前一样,我们检查open是否成功。如果成功,将文件传递给一个函数,该函数负责读取并处理输入数据。如果open失败,打印一条错误信息并继续处理下一个文件。因为input是while循环的局部变量,它在每个循环步中都要创建和销毁一次。当一个fstream对象离开其作用域时,与之关联的文件会自动关闭。在下一步循环中,input会再次被创建。

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

8.2.2 文件模式

每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。下面列出了文件模式和它们的含义。

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

无论用哪种方式打开文件,我们都可以指定文件模式,调用open成员函数显式打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。

有的文件模式可以同时指定。
通过|可以组合文件模式,比如: ofstream::out | ofstream::trunc

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

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

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

  • ifstream 关联的文件默认以 in 模式打开;
  • ofstream 关联的文件默认以 out 模式打开(虽然默认参数没有 trunc ,但是实际上同时使用了);
  • 与 fstream 关联的文件默认以 in 和 out 模式打开。

以out模式打开文件会丢弃已有数据

默认情况下,当我们打开一个ofstmam时,文件的内容会被丢弃。

// 在这几条语句中,filel都被截断
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);

Note: 保留被ofstream对象打开的文件中已有数据的唯一方法是显式指定 app 或 in 模式。因为 ofstream 关联的文件默认以 out 模式打开,会抹去文件已存在的数据。

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

对于一个给定流,每当(显式或隐式)打开文件时,都可以改变其文件模式。

ofstream out;// 未指定文件打开模式
out.open("scratchpad");// 模式隐合设置为out和trunc
out.close();// 关闭out,以便将其用于其他文件
out.open("precious", ofstream::app);// 模式为out和app,默认是out
out.close();

第一个open调用未显式指定输出模式,文件隐式地以 out 模式打开。通常情况下,使用 out 模式意味着同时使用 trunc 模式。因此,当前目录下名为scratchpad的文件的内容将被清空。当打开名为precious的文件时,我们指定了 append 模式(app)。文件中已有的数据都得以保留,所有写操作都在文件末尾进行。

Note: 在每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式地设置。当程序未指定模式时,就使用默认值。


8.3 string 流

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

头文件sstream中定义的这些类型的单参数构造函数都是explicit的,,即不能通过隐式转换参数。

下面列出了一部分特有操作,可以对string流对象调用这些操作,但不能对其他IO类型调用这些操作。

头文件 sstream 定义的类型的特有操作详解
ss strm;strm 是一个未绑定的string流对象。 ss 指代头文件 sstream 中定义的一个类型
ss strm(s);strm 是一个ss对象,保存 string 对象 s 的一个拷贝。此构造函数是explicit的
strm.str()返回 strm 所保存的 string 的拷贝
strm.str(s)将 string 对象 s 拷贝到 strm 中,并返回 void 。

8.3.1 使用 istringstream

当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用 istringstream 。

考虑这样一个例子,假定有一个文件,列出了一些人和他们的电话号码。某些人只有一个号码,而另一些人则有多个——家庭电话、工作电话、移动电话等。这种输入文件看起来可能是这样的:

文件中每条记录都以一个人名开始,后面跟随一个或多个电话号码。
morgan 	2015552368 8625550123 
drew	9735550130 
lee		6095550132 2015550175 8005550000

首先定义一个简单的类来描述输入数据:

// struct 成员默认为公有
struct Personinfo {
	string name; 
	vector<string> phones;
};

类型Personinfo的对象会有一个成员来表示人名,还有一个vector容器来保存此人的所有电话号码。

下面程序会读取数据文件,并创建一个储存Personinfo的 vector 容器。 vector 中每个元素对应文件中的一条记录。我们在一个循环中处理输入数据,每个循环步读取一条记录,提取出一个人名和若干电话号码:

string line,word;				// 分别来自输入的一行和单词
vector<PersonInfo> people;		// 保存来自输入的所有记录
// 逐行从输入读取数据,直至cin遇到文件尾(或其他错误)
while (getline(cin,line)){
	PersonInfo info;			// 创建一个保存此记录数据的对象
	istringstream record(line);	// 将记录绑定到刚刚读入的行
	record >> info.name;		// 读取名字
	while (record >> word)
		info.phones.push_back(word);	// 保持它们
	people.push_back(info);		// 将记录追加到people的末尾
}

这里用 getline 从标准输入读取整条记录。如果 getline 函数调用成功,那 么 line 中将保存着从输入文件而来的一条记录。在 while 中定义了一个局部Personlnfo对象,来保存当前记录中的数据。
接下来将一个 istringstream 与刚刚读取的文本行进行绑定,这样就可以在 此 istringstream 上使用输入运算符来读取当前记录中的每个元素。首先读取人名,随后用一个 while 循环读取此人的电话号码。
当读取完line中所有数据后,内层 while 循环就结束了。此循环的工作方式与前面章节中读取 cin 的循环很相似,不同之处是,此循环从一个 string 而不是标准输入 读取数据。当 string 中的数据全部读出后,同样会触发“文件结束”信号,在 record 上的下一个输入操作会失败。
将刚刚处理好的Personinfo追加到 vector 中,外层 while 循环的一个循环步就随之结束了。外层 while 循环会持续执行,直至遇到 cin 的文件结束标识。

8.3.2 使用 ostringstream

当逐步构造输出,希望最后一起打印时, ostringstream 是很有用的。例如,对上一节的例子,可能想逐个验证电话号码并改变其格式。如果所有号码都是有效的,则希望输出一个新的文件,包含改变格式后的号码。对于那些无效的号码,我们不会将它们输出到新文件中,而是打印一条包含人名和无效号码的错误信息。
由于不希望输出有无效电话号码的人,因此对每个人,直到验证完所有电话号码后才可以进行输出操作。但是可以先将输出内容“写入”到一个内存 ostringstream 对象中:

for (const auto &entry : people)			// 对people中每一项
{
	ostringstream formatted, badNums;		// 每个循环步创建一个对象
	for (const auto &nums : entry.phones)	// 对每个数
	{
		if (!validTel(nums))
			badNums << " " << nums;			// 将数的字符串形式存入badNums
		else	
			// 将格式化的字符串"写入"formatted
			formatted << " " << (nums);
	}
	if (badNums.str().empty())		// 没有错误的数
		os  << entry.name << " "	// 打印名字到os绑定的string流中
			<< formatted.str() << endl;	// 与格式化的数
	else
		// 否则,打印名字和错误的数
		cerr << "input error: " << entry.name
		 	 << " invalid number(s) " << badNums.str() << endl;
}

在此程序中,假定已有两个函数,valid和format,分别完成电话号码验证和改变格式的功能。程序最有趣的部分是对字符串流formatted和badNums的使用。我们使用标准的输出运算符(<<)向这些对象写入数据,但这些“写入操作实际上转换为 string 操作,分别向formatted和badNums中的 string 对象添加字符。

YOLOv8【第十章:多任务扩展深度篇·第18节】不规则工业缺陷分割——针对划痕、裂纹的 Seg 模型调优! 🏆 本文收录于 《YOLOv8实战:从入门到深度优化》 专栏。该专栏系统复现并梳理全网各类 YOLOv8 改进与实战案例(当前已覆盖分类 / 检测 / 分割 / 追踪 / 关键点 / OBB 检测等方向),坚持持续更新 + 深度解析,质量分长期稳定在 97 分以上,可视为当前市面上 覆盖较全、更新较快、实战导向极强 的 YOLO 改进系列内容之一。 阅读详情

相关推荐

UEFI开发探索102 – ACPI探究01(UEFI配置表)

(请保留-> 作者: 罗冰 https://blog.youkuaiyun.com/luobing4365) ACPI探究01(UEFI配置表)1 大致规划2 UEFI Configuration Table(配置表)3 代码实现 最近有项工作,是修改ACPI表。问题本身并不复杂,但是由于代码需要移植到Option ROM上,遇到不少奇怪的现象。因此,花了不少时间,对ACPI进行研究。 任务是完成了,我的好奇心又被勾起来了。准备发挥“格物致知”的信念,把我认为的ACPI各方面的知识,好好地捋一捋。 1 大

8244

C++头文件sstream笔试常见用法

C++头文件sstream笔试常见用法,本文主要介绍一些实用用法。

zdlzf的博客 4101

SSL证书验证:VMware Horizon Client的安全隐患与临时解决方案

本文深入分析了VMware Horizon Client中SSL证书验证失败的安全隐患,提供了临时解决方案与长期防御策略。针对自签名证书、证书链断裂等常见问题,文章详细解析了风险成因,并给出分级响应方案,帮助企业平衡安全与效率。通过实战案例和自动化脚本,指导IT管理员构建主动防御体系,有效降低中间人攻击风险。

weixin_29290863的博客 337

ostringstream的使用方法

ostringstream的使用方法 【本文来自】http://www.builder.com.cn/2003/0304/83250.shtml http://www.cppblog.com/alantop/archive/2007/07/10/27823.html 使用stringstream对象简化类型转换 C++标准库中的&lt;sstream&gt;提供了比ANSI C...

weixin_33916256的博客 474

c++通过自然语言处理技术分析语音信号音高

其中,缓冲区大小为2048,最小基频为80Hz,最大基频为1000Hz。通常情况下,音高在85Hz到255Hz之间,因此可以根据实际情况修改最小和最大基频值。C++中可以使用数字信号处理库或语音处理库,比如MATLAB、Praat、YIN算法等等来进行音高分析。对于语音信号的音高分析,可以使用基频提取技术。基频是指一个声音周期的重复率,也就是一个声音波形中最长的周期。通常情况下,人的声音基频范围是85Hz到255Hz。这段代码使用了YIN算法来计算语音信号的基频,得出的结果即为音高。

SYC20110120的博客 1727

数学:箭头符号→(to)都表示什么意义?

数学符号“→”在不同领域有不同含义:在逻辑中表示“蕴含”(p→q);在函数中表示映射关系(f:A→B);在极限中表示变量趋近(x→a);在推导过程中表示推理方向(A→B)。此外,它也可能用于向量表示或数列趋势。其具体含义需结合上下文判断,核心应用场景包括逻辑运算、函数定义、极限分析和数学推导等。

shunzi2016的博客 5558

C++ string流:istringstream、ostringstream、stringstream

C++ string流:istringstream/ostringstream/stringstream **string流 ** 定义在头文件#include < sstream >中,头文件定义三个类型(istringstream,ostringstream,stringstream)来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个...

胡椒面er 655

KEPServer EX6的Modbus、MQTT和REST Server一站式配置整理说明

KEPServer EX6的Modbus、MQTT和REST Server一站式配置整理说明

syqenter的专栏 8960

c++primer 第八章 IO库

8.1 IO类 8.2 文件输入输出流 8.3 string流

qq_36183881的博客 532

YOLOv11【第二章:核心模块进阶篇·第28节】DynamicHead动态检测头设计!

🏆 本文收录于专栏 《YOLOv11实战:从入门到深度优化》。本专栏围绕 YOLOv11 的改进、训练、部署与工程优化 展开,系统梳理并复现当前主流的 YOLOv11 实战案例与优化方案,内容目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。

**My Coding Family** 1万+

小目标检测之改进的YOLOV11算法

MASF-YOLO通过多尺度特征聚合注意力增强自适应融合三大核心创新,显著提升了无人机视角下的小目标检测性能。

hasakie的博客 1715

《C++Primer》第8章 IO类 笔记

1、几种标准库IO类型 几种类型间的继承关系,如下图: 2、IO类型无拷贝和赋值 这也是为什么这种类型作为函数返回值时,返回的是引用。 3、IO类的条件状态 条件状态是指:一组标志和函数,可以告诉我们指定的 (IO) 流是否可用,也就是我们刚刚的输入合不合法,这组标志和函数可被任何IO类使用。 下面的表列出了所有的条件状态(标志和函数)。 其中 “strm” 代指所有的IO类。 置位:强制置1 复位:强制置0 当输入出现错误时,我们可以用上面的函数和标志位检查出来,如下面例子所示: cou

m0_51551385的博客 771

华为OD机试真题2026双机位C卷 C语言 实现【明日之星选举】

给定一组选票votes。votes[i]代表第 i 张选票的内容,包含一个字符串"zhangsan,hanmei",表示推荐zhangsan为明日之星,反对 hanmei 称为明日之星。赞成或反对使用逗号(,)分割。 给定一个数组n,表示最后将筛选出赞成票最多的n个人,如果赞成票相同,则反对票越少,排序越靠前;如果赞成票和反对票都相同,则按照姓名字典序升序排序。

MISAYAONE的博客 182

C++ Primer(第五版)|练习题答案与解析(第八章:IO库)

C++ Primer(第五版)|练习题答案与解析(第八章:IO库) 本博客主要记录C++ Primer(第五版)中的练习题答案与解析。 参考:C++ Primer C++ Primer 练习题8.1 编写函数,接受一个istream &参数,返回值也是istream&。此函数必须从给定流中读取数据,直至遇到文件结束标识符时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返...

qq_24739717的博客 503

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

C++ Primer 第五版 第八章 IO库

如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,即返回空指针。当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流都定义了一个名为open的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。此外,一些类,如string,通常也会定义类似的IO操作,来读写自己的对象。

weixin_44500921的博客 566

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

《C++primer(第五版)》学习之路-第八章:IO库

【 声明:版权所有,转载请标明出处,请勿用于商业用途。  联系信箱:libin493073668@sina.com】 8.1 IO类 1. istream(输入流)类型,提供输入操作 ostream(输出流)类型,提供输出操作 cin,一个istream对象,从标准输入读取数据 cout,一个ostream对象,向标准输出写入数据 cerr,一个ostream对象,通

月下的寂寞肖邦 2011

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C++ primer 第五版个人笔记 第八章 IO库

8.1 IO类 IO库主要有三种流类型,普通流、文件流(fstream)、string流(sstream),以及char或宽字符版本; 不能拷贝或对IO对象赋值,因此不能讲形参或函数返回类型设置为流类型,通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const ofstream out1, out2; out1 = out2; //错误...

Mint2yx4的博客 261

C++Primer第五版【笔记】——第八章 IO库

1. IO类 IO类和IO对象操作的实际上是char数据(字符数据流)。默认情况下IO对象是与用户控制台窗口绑定,即从控制台窗口读数据或写数据。实际上,char数据的来源还可以来自文件,或一个string。此外,C++还支持宽字符(wide-character)的读写。 表一:IO库类型和头文件 Header Type iostream istream, w

学海无涯,快乐行舟 2924

C++Primer第五版 习题答案 第八章 IO库

终于到第二部分,给爷冲,干就完了! 8.1 std::istream& iiofunction(std::istream& is) { std::string s; while (is>>s) { std::cout << s << std::endl; } is.clear(); return is; } 8.2 #include<iostream> #include<string> std::istream&

qq_24898229的博客 1242

《c++primer》第五版笔记:第八章IO库

c++IO

weixin_46266058的博客 469

C++primer(第五版)第八章(IO库)

未指定文件打开模式时,ifstream默认以in模式打开,ofstream默认以out模式打开,fstream默认以in|out模式打开(多种模式可以用(或)|来连接表示用多种方式打开文件).如果文件流对象被创建在一个作用域内(例如在循环体里),那么出了作用域则文件流对象被销毁,close()会自动被调用.每个输出流都管理一个缓冲区,用来保存程序读写的数据.缓冲区不刷新,数据就会一直停留在缓冲区.一个输出流关联另一个流,当读写被关联的流时,关联的流的缓冲区会被刷新.以out模式打开文件,会清空原文件内容.

m0_63235356的博客 225

C++ Primer (第五版)-第八章 IO库

C++ PRIMER IO库

jiyanghao19的博客 212

C++Primer第五版 习题答案 第八章 IO库(The IO Library)

8.1 std::istream &amp;amp;iofunction(std::istream &amp;amp;is) { std::string s; while(is &amp;gt;&amp;gt; s) std::cout &amp;lt;&amp;lt; s &amp;lt;&amp;lt; std::endl; is.clear(); return is; } 8.2

shamozhizhoutx的博客 3431

《C++ primer》第五版习题答案整理——第八章 IO库

《C++ primer》第五版习题答案整理——第八章 IO库

SuperKing 265

C++primer第五版笔记-第八章IO库

1.IO类 #include istream//从流中读取数据 ostream//从流中写入数据 iostream//读写流 #include //文件 ifstream ofstream fstream #include //字符串 istringstream ostringstream iostringstream fstream和sstream都继承于iostream,所以

liugallup的专栏 487

c++ primer 第五版学习笔记-第八章 IO库

本文为转载,出处:https://blog.youkuaiyun.com/libin1105/article/details/48675765 https://blog.youkuaiyun.com/sunhero2010/article/details/49799679 https://b...

一只啃着鸡翅写代码的白猫的成长记录 283
上一篇: 《C++Primer 第五版》——第七章 类
下一篇: 《C++Primer 第五版》——第九章 顺序容器
486L
博客等级 码龄7年 20粉丝 117原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值