8.IO库

IO

8.1IO

在这里插入图片描述

为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。

8.1.1IO对象无拷贝或赋值

不能拷贝或对IO对象赋值

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

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

8.1.2条件状态

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

一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,才可以从它读取/写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态:

while (cin >> word) {
    // ok:读操作成功
}
查询流的状态

将流作为条件使用,只能告知流是否有效,而无法告知具体发生了什么。有时也需要知道流为什么会失败。例如,在键入文件结束标识后的应对措施,可能与遇到一个IO设备错误的处理方式是不同的。
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用。IO库定义了4个iostate类型的constexpr值,表示特定的位模式。这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标志位:

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

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

  • 操作good在所有错误位均未置位的情况下返回true
  • badfaileof则在对应错误位被置位时返回true
  • 此外,在badbit被置位时,fail也会返回true。这意味着,使用goodfail是确定流的总体状态的正确方法。实际上,将流当做条件使用的代码就等价于!fail()。而eofbad操作只能表示特定的错误。
管理条件状态
  • 流对象的rdstate成员返回一个iostate值,对应流的当前状态。
  • setstate操作将给定条件位置位,表示发生了对应错误。
  • clear成员是一个重载的成员:它有一个不接受参数的版本,而另一个版本接受一个iostate类型的参数。

clear不接受参数的版本复位所有错误标志位。执行clear后,调用good会返回true

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

带参数的clear版本接受一个iostate值,表示流的新状态,即将要设为当前状态的标志值。为了复位单一的条件状态位,首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。
下面的代码将failbitbadbit复位,但保持eofbit不变。

// 复位failbit和badbit,保持其他位标志不变。其中,failbit值为0x4(0100);badbit值为0x1(0001)。
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
8.1.3管理输出缓冲

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

os << "please enter a value: ";

文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作,这样可以带来很大的性能提升。
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:

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

操纵符endl完成换行并刷新缓冲区的工作;flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。

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

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

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

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

关联输入和输出流

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

cin >> ival;

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

tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个输出流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream(带参数的版本返回操作前的绑定流)。
既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream

cin.tie(&cout); // 仅仅是用来展示:标准库已经将cin和cout关联在一起
// old_tie指向当前关联到cin的流(如果有的话)
ostream *old_tie = cin.tie(nullptr);    // cin不再与其他流关联,需要注意的是,带参数的版本返回操作前的绑定流,在这即为cout
cin.tie(&cerr); // 读取cin会刷新cerr而不是cout
cin.tie(old_tie);   // 重建cin和cout间的正常关联

每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream

8.2文件输入输出

除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员来管理与流关联的文件。
在这里插入图片描述

8.2.1使用文件流对象

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

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

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

fstream代替iostream&

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

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()) {	// 检查isbn
            total.combine(trans);	// 更新销售总额
        } else {
            print(output, total) << endl;	// 打印结果
            total = trans;	// 处理下一本书
        }
    }

    print(output, total) << endl;	// 打印最后一本书的销售额
} else {	// 文件中无输入数据
    cerr << "No data?!" << endl;
}
成员函数openclose

如果定义了一个空文件流对象,可以随后调用open来将它与文件关联起来:

ifstream in(ifile);
ofstream out;
out.open(ifile + ".copy");

如果open调用失败,则failbit会被置位。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯:if (out)
一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件调用open会失败,并会导致failbit被置位。随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,可以打开新的文件:

in.close();
in.open(ifile + "2");

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

自动构造和析构

考虑这样一个程序,它的main函数接受一个要处理的文件列表:

for (auto p = argv + 1; p != argv + argc; ++p) {
    ifstream input(*p);

    if (input) {    // 检查open是否成功
        process(input); // 如果文件打开成功,处理此文件
    } else {
        cerr << "couldn't open: " + string(*p);
    }
}   // 每个循环步input都会离开作用域,因此会被销毁

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

8.2.2文件模式

在这里插入图片描述

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

  • 只可以对ofstreamfstream对象设定out模式。
  • 只可以对ifstreamfstream对象设定in模式。
  • 只有当out也被设定时才可以设定trunc模式。
  • 只要trunc没被设定,就可以设定app(append)模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
  • 默认情况下,即使没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
  • atebinary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
out模式打开文件会丢弃已有数据

默认情况下,当打开一个ofstream时,文件的内容会被丢弃。阻止一个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打开的文件中已有数据的唯一方法是显示指定appin模式。

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

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

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

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

8.3string

在这里插入图片描述

8.3.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末尾
}
8.3.2使用ostringstream

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

for (const auto &entry : people) {	// 对people中的每一项
	// 每个循环步创建的对象,先将输出内容写入到一个内存ostringstream 中
    ostringstream formatted, badNums;

    for (const auto &nums : entry.phones) {	// 对每个数
        if (!valid(nums)) {
            badNums << " " << nums;	// 将数的字符串形式存入badNums
        } else {
        	// 将格式化的字符串写入formatted 
            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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值