C++ primer第八章—IO库

本文详细介绍了C++中的IO类,包括istream、ostream、cin、cout等对象的使用,以及IO对象的条件状态管理和输出缓冲管理。特别讨论了文件输入输出,如ifstream、ofstream和fstream的使用,以及文件模式的设置。另外,还讲解了string类在istringstream和ostringstream中的应用,展示了如何从string读取和写入数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

8.1 IO类

  1. 部分IO库设施
设施作用
istream输入流类型,提供输入操作
ostream输出流类型,提供输出操作
cinistream 对象,从标准输入读取数据
coutostream 对象,向标准输出写入数据
cerrostream 对象,向标准错误写入数据
>> 运算符从 istream 对象读取输入数据
<< 运算符向 ostream 对象写入输出数据
getline 函数从 istream 对象读取一行数据,写入 string 对象
  1. 头文件 iostream 定义了用于读写流的基本类型,fstream 定义了读写命名文件的类型,sstream 定义了读写内存中 string 对象的类型。
    在这里插入图片描述
  2. 宽字符版本的IO类型和函数的名字以 w 开始,如 wcin、wcout 和 wcerr 分别对应 cin、cout 和cerr。它们与其对应的普通 char 版本都定义在同一个头文件中,如头文件 fstream 定义了 ifstream 和 wifstream 类型。
  3. 通常可以将一个派生类(继承类)对象当作其基类(所继承的类)对象来使用,这是通过继承机制(inheritance) 实现的

8.1.1 IO对象无拷贝或赋值

ofstream out1, out2;
out1 = out2;    			// 错误:不能对流对象赋值
ofstream print(ofstream);   // 错误:不能初始化ofstream参数
out2 = print(out2);     	// 错误:不能拷贝流对象
  1. 由于IO对象不能拷贝,因此不能将函数形参或返回类型定义为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是 const 的。

8.1.2 条件状态

  1. IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。
  2. IO库条件状态:
    在这里插入图片描述
    在这里插入图片描述
// 错误例子
int val;
cin >> val;
// 正确例子
while (cin >> word)
    // ok: 读操作成功....
  1. badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法继续使用了。在发生可恢复错误后,failbit 会被置位,如期望读取数值却读出一个字符。如果到达文件结束位置,eofbit 和 failbit 都会被置位。如果流未发生错误,则 goodbit 的值为0。如果 badbit、failbit 和 eofbit 任何一个被置位,检测流状态的条件都会失败。
  2. good 函数在所有错误均未置位时返回 true。而 bad、fail 和 eof 函数在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail 函数也会返回 true。因此应该使用 good 或 fail 函数确定流的总体状态,eof 和 bad 只能检测特定错误。
  3. 流对象的 rdstate 成员返回一个 iostate 值,表示流的当前状态。setstate 成员用于将指定条件置位(叠加原始流状态)。clear 成员的无参版本清除所有错误标志;含参版本接受一个 iostate 值,用于设置流的新状态(覆盖原始流状态)。
// 记住cin的当前状态
auto old_state = cin.rdstate();     // 记住cin的当前状态
cin.clear();    			// 使cin有效
process_input(cin);     	// 使用cin
cin.setstate(old_state);    // 将cin置为原有状态
  1. 带参数的clear版本接受一个iostate值,表示流的新状态。为了复位单一的条件状态位,我们首先使用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。例如,下面代码将failbit和badbit复位,但保持eofbit不变。
// 复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

8.1.3 管理输出缓冲

  1. 每个输出流都管理一个缓冲区,用于保存程序读写的数据。
  2. 导致缓冲刷新(即数据真正写入输出设备或文件)的原因有很多:
  • 程序正常结束。
  • 缓冲区已满。
  • 使用操作符(如endl)显示刷新缓冲区。
  • 在每个输出操作后,可以用unitbuf操纵符设置流的内部状态,从而清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的。
  • 一个输出流可以被关联到另一个流。这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,cin 和 cerr 都关联到 cout,因此,读 cin 或写 cerr 都会刷新 cout 的缓冲区。
  1. flush 操纵符刷新缓冲区,但不输出任何额外字符。ends 向缓冲区插入一个空字符,然后刷新缓冲区。
cout << "hi!" << endl;   // 输出hi和一个换行,然后刷新缓冲区
cout << "hi!" << flush;  // 输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends;   // 输出hi和一个空字符,然后刷新缓冲区
  1. 如果想在每次输出操作后都刷新缓冲区,可以使用 unitbuf 操纵符。它令流在接下来的每次写操作后都进行一次 flush 操作。而 nounitbuf 操纵符则使流恢复使用正常的缓冲区刷新机制。
cout << unitbuf;    // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf;  // 回到正常的缓冲方式
  1. 如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后, 它所输出的数据很可能停留在输出缓冲区中等待打印。当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则, 可能将大量时间浪费在追踪代码为什么没有执行上,而实际上代码已经执行了,只是程序崩溃后缓冲区没有被刷新,输出数据被挂起没有打印而已。
  2. 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此下面的语句会导致cout的缓冲区被刷新:
cin >> ival;
  1. 交互式系统通常应该关联输入流和输出流。这意味着包括用户提示信息在内的所有输出,都会在读操作之前被打印出来。
  2. 使用 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间的正常关联
  1. 每个流同时最多关联一个流,但多个流可以同时关联同一个 ostream。向 tie 传递空指针可以解开流的关联。

8.2 文件输入输出

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

8.2.1 使用文件流对象

  1. 每个文件流类型都定义了open函数,它完成一些系统操作,定位指定文件,并视情况打开为读或写模式。
  2. 创建文件流对象时,如果提供了文件名(可选),open 会被自动调用。
ifstream in(ifile);   	// 构造一个ifstream并打开给定文件
ofstream out;   		// 输出文件流未关联到任何文件
  1. 在C++11中,文件流对象的文件名可以是 string 对象或C风格字符数组。旧版本的标准库只支持C风格字符数组。
  2. 在要求使用基类对象的地方,可以用继承类型的对象代替。因此一个接受 iostream 类型引用或指针参数的函数,可以用对应的 fstream 类型来调用。
ifstream input(grgv[1]);
ifstream output(grgv[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(out, total) << endl;
			total = trans;
		}
	}
	print(output, total) << endl;
}
else{
	cerr << "No data?!" << endl;
}
  1. 可以先定义空文件流对象,再调用 open 函数将其与指定文件关联。如果 open 调用失败,failbit 会被置位。
ifstream in(ifile);    // 构筑一个ifstream并打开给定文件
ofstream out;    // 输出文件流未于任何文件相关联
out.open(ifile+".copy");    // 打开指定文件
  1. 一旦一个文件流已经打开,它就保持与对应文件的关联。对一个已经打开的文件流调用 open 会失败,并导致 failbit 被置位。随后试图使用文件流的操作都会失败。如果想将文件流关联到另一个文件,必须先调用 close 关闭当前文件,再调用 clear 重置流的条件状态(close 不会重置流的条件状态)。
if(out)  // 检查open是否成功
in.close();    // 关闭文件
in.open(ifile + "2")  // 打开另一个文件
  1. 当 fstream 对象被销毁时,close 会自动被调用。

8.2.2 文件模式

  1. 每个流都有一个关联的文件模式,用来指出如何使用文件。
    在这里插入图片描述
  2. 指定文件模式有如下限制:
  • 只能对 ofstream 或 fstream 对象设定 out 模式。
  • 只能对 ifstream 或 fstream 对象设定 in 模式。
  • 只有当 out 被设定时才能设定 trunc 模式。
  • 只要 trunc 没被设定,就能设定 app 模式。在 app 模式下,即使没有设定 out 模式,文件也是以输出方式打开。
  • 默认情况下,即使没有设定 trunc,以 out 模式打开的文件也会被截断。如果想保留以 out 模式打开的文件内容,就必须同时设定 app 模式,这会将数据追加写到文件末尾;或者同时设定 in 模式,即同时进行读写操作。
  • ate 和 binary 模式可用于任何类型的文件流对象,并可以和其他任何模式组合使用。
  • 与 ifstream 对象关联的文件默认以 in 模式打开,与 ofstream 对象关联的文件默认以 out 模式打开,与 fstream 对象关联的文件默认以 in 和 out 模式打开。
  1. 每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与 ifstream 关联的文件默认以 in 模式打开;与 ofstream 关联的文件默认以 out 模式打开;与 fstream 关联的文件默认以 in 和 out 模式打开。
  2. 默认情况下,打开 ofstream 对象时,文件内容会被丢弃,阻止一个 ofstream 清空给定文件内容的方法是同时指定 app 模式:
// 在这几条语句中,file1都被截断
ofstream out("file1");  // 隐含以输出模式打开文件并截断文件
ofstream out2("file1", ofstream::out);  // 隐含地截断文件
ofstream out2("file1", ofstream::out | ofstream::trunc);
// 为保留文件内容,我们必须显式指定app模式
ofstream out2("file1", ofstream::app);  // 隐含为输出模式
ofstream out2("file1", ofstream::out | ofstream::app);
  1. 流对象每次打开文件时都可以改变其文件模式。
ofstream out;   			// 未指定文件打开模式
out.open("scratchpad");    	// 模式隐含设置为输出和截断
out.close();    			// 关闭out,以便我们将其用于其他文件
out.open("precious", ofstream::app);   // 模式为out和app
out.close();
  1. 保留被 ofstream 打开的文件中已有数据的唯一方法是显式指定 app 或 in 模式。

8.3 string类

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

8.3.1 使用istringstream

  1. 当某些工作是对整行文本进行处理, 而其他一些工作是处理行内的单个单词时,通常可以使用 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末尾
}

8.3.2 使用ostringstream

  1. 当逐步构造输出, 希望最后一起打印时, 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值