《C++ Primer》学习笔记(八):标准 IO 库

欢迎关注WX公众号:【程序员管小亮】

专栏C++学习笔记

《C++ Primer》学习笔记/习题答案 总目录

——————————————————————————————————————————————————————

📚💻 Cpp-Prime5 + Cpp-Primer-Plus6 源代码和课后题

标准 IO 库

部分IO库设施:

  • istream:输入流类型,提供输入操作。
  • ostream:输出流类型,提供输出操作。
  • cinistream 对象,从标准输入读取数据。
  • coutostream 对象,向标准输出写入数据。
  • cerrostream 对象,向标准错误写入数据。
  • >> 运算符:从 istream 对象读取输入数据。
  • << 运算符:向 ostream 对象写入输出数据。
  • getline 函数:从 istream 对象读取一行数据,写入 string 对象。

1、IO类

头文件 iostream 定义了用于读写流的基本类型,fstream 定义了读写命名文件的类型,sstream 定义了读写内存中 string 对象的类型。
在这里插入图片描述
宽字符版本的IO类型和函数的名字以 w 开始,如 wcinwcoutwcerr 分别对应 cincoutcerr。它们与其对应的普通 char 版本都定义在同一个头文件中,如头文件 fstream 定义了 ifstreamwifstream 类型。

通常可以将一个派生类(继承类)对象当作其基类(所继承的类)对象来使用,这是通过 继承机制(inheritance) 实现的。

1)IO象无拷贝或赋值

不能拷贝或对IO对象赋值。

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

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

2)条件状态

IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。

IO库条件状态:
在这里插入图片描述
在这里插入图片描述

while (cin >> word)
    // ok: 读操作成功....

badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法继续使用了。在发生可恢复错误后,failbit 会被置位,如期望读取数值却读出一个字符。如果到达文件结束位置,eofbitfailbit 都会被置位。如果流未发生错误,则 goodbit 的值为0。如果 badbitfailbiteofbit 任何一个被置位,检测流状态的条件都会失败。

good 函数在所有错误均未置位时返回 true。而 badfaileof 函数在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail 函数也会返回 true。因此应该使用 goodfail 函数确定流的总体状态,eofbad 只能检测特定错误。

流对象的 rdstate 成员返回一个 iostate 值,表示流的当前状态。setstate 成员用于将指定条件置位(叠加原始流状态)。clear 成员的无参版本清除所有错误标志;含参版本接受一个 iostate 值,用于设置流的新状态(覆盖原始流状态)。

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

3)管理输出缓冲

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

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

  • 程序正常结束。
  • 缓冲区已满。
  • 使用操纵符(如 endl)显式刷新缓冲区。
  • 在每个输出操作之后,可以用 unitbuf 操纵符设置流的内部状态,从而清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的。
  • 一个输出流可以被关联到另一个流。这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,cincerr 都关联到 cout,因此,读 cin 或写 cerr 都会刷新 cout 的缓冲区。

flush 操纵符刷新缓冲区,但不输出任何额外字符。ends 向缓冲区插入一个空字符,然后刷新缓冲区。

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

如果想在每次输出操作后都刷新缓冲区,可以使用 unitbuf 操纵符。它令流在接下来的每次写操作后都进行一次 flush 操作。而 nounitbuf 操纵符则使流恢复使用正常的缓冲区刷新机制。

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

如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后, 它所输出的数据很可能停留在输出缓冲区中等待打印。

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

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

cin >> ival;

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

使用 tie 函数可以关联两个流。它有两个重载版本:无参版本返回指向输出流的指针。如果本对象已关联到一个输出流,则返回的就是指向这个流的指针,否则返回空指针。tie 的第二个版本接受一个指向 ostream 的指针,将本对象关联到此 ostream

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间的正常关联

每个流同时最多关联一个流,但多个流可以同时关联同一个 ostream。向 tie 传递空指针可以解开流的关联。

2、文件输入输出

头文件 fstream 定义了三个类型来支持文件IO:ifstream 从给定文件读取数据,ofstream 向指定文件写入数据,fstream 可以同时读写指定文件。
在这里插入图片描述

1)使用文件流对象

每个文件流类型都定义了open函数,它完成一些系统操作,定位指定文件,并视情况打开为读或写模式。

创建文件流对象时,如果提供了文件名(可选),open 会被自动调用。

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

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

在要求使用基类对象的地方,可以用继承类型的对象代替。因此一个接受 iostream 类型引用或指针参数的函数,可以用对应的 fstream 类型来调用。

可以先定义空文件流对象,再调用 open 函数将其与指定文件关联。如果 open 调用失败,failbit 会被置位。

一旦一个文件流已经打开,它就保持与对应文件的关联。对一个已经打开的文件流调用 open 会失败,并导致 failbit 被置位。随后试图使用文件流的操作都会失败。如果想将文件流关联到另一个文件,必须先调用 close 关闭当前文件,再调用 clear 重置流的条件状态(close 不会重置流的条件状态)。

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

2)文件模式

每个流都有一个关联的文件模式,用来指出如何使用文件。
在这里插入图片描述
指定文件模式有如下限制:

  • 只能对 ofstreamfstream 对象设定 out 模式。
  • 只能对 ifstreamfstream 对象设定 in 模式。
  • 只有当 out 被设定时才能设定 trunc 模式。
  • 只要 trunc 没被设定,就能设定 app 模式。在 app 模式下,即使没有设定 out 模式,文件也是以输出方式打开。
  • 默认情况下,即使没有设定 trunc,以 out 模式打开的文件也会被截断。如果想保留以 out 模式打开的文件内容,就必须同时设定 app 模式,这会将数据追加写到文件末尾;或者同时设定 in 模式,即同时进行读写操作。
  • atebinary 模式可用于任何类型的文件流对象,并可以和其他任何模式组合使用。
  • ifstream 对象关联的文件默认以 in 模式打开,与 ofstream 对象关联的文件默认以 out 模式打开,与 fstream 对象关联的文件默认以 inout 模式打开。

每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与 ifstream 关联的文件默认以 in 模式打开;与 ofstream 关联的文件默认以 out 模式打开;与 fstream 关联的文件默认以 inout 模式打开。

默认情况下,打开 ofstream 对象时,文件内容会被丢弃,阻止一个 ofstream 清空给定文件内容的方法是同时指定 app 模式:

流对象每次打开文件时都可以改变其文件模式。

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

保留被 ofstream 打开的文件中已有数据的唯一方法是显式指定 appin 模式。

3、string流

头文件 sstream 定义了三个类型来支持内存IO:istringstreamstring 读取数据,ostringstreamstring 写入数据,stringstream 可以同时读写 string 的数据。
在这里插入图片描述

1)使用istringstream

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

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

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末尾
}

2)使用ostringstream

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

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

参考文章

  • 《C++ Primer》
发布了241 篇原创文章 · 获赞 5143 · 访问量 85万+

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 酷酷鲨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览