「生活可以更简单, 欢迎来到我的开源世界」
  1. 8.1 IO类
    1. 8.1.1 IO对象无拷贝或赋值
    2. 8.1.2 条件状态
    3. 8.1.3 管理输出缓冲
  2. 8.2 文件输入输出
  3. 8.3 string流
CppPrimer-第8章-IO库
2018-06-21
」 「

C++语言不直接处理输入输出,而是通过一组定义在标准库中的类型来处理IO。

IO库定义了读写内置类型值的操作。

8.1 IO类

iostream定义了用于读写流的基本类型

fstream定义了读写命名文件的类型

sstream定义了读写内层string对象的类型

image-20210115163953518

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

标准库通过继承机制,使我们能忽略不同类型的流之间的差异。

8.1.1 IO对象无拷贝或赋值

不能将形参或返回类型设置为流类型。

进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

8.1.2 条件状态

IO操作可能发生错误,IO类定义了一些函数和标志,帮助我们访问和操纵流的条件状态。

image-20210115164926483image-20210115164941636

一个流一旦发生错误,其后续的IO操纵都会失败。只有当一个流处于无措状态时,我们才可以从它读取数据,向它写入数据。

由于流可能处于错误状态,代码通常需要在使用之前检查其状态:

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

将流作为条件使用,只能知道流是否有效,无法知道具体发生了什么。

IO库定义了一个机器无关的iostate类型提供表达流状态的完整功能,这个类型应作为一个位集合来使用。

IO库定义了4个iostate类型的constexpr值表示特定的位模式:

badbitfailbiteofbit任一个被置位,则检测流状态的条件会失败。

8.1.3 管理输出缓冲

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

有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。

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

刷新输出缓冲区:

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

如果程序异常终止,输出缓冲区是不会被刷新的。当调试一个已经奔溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了,否则可能会浪费大量时间在追踪代码为什么没有执行上。

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。

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

tie有两个重载版本:

可以将一个istream对象关联到一个ostream,也可以将一个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间的正常关联

8.2 文件输入输出

头文件fstream定义了三个类型支持文件IO:

fstream继承了来自iostream的操作,还定义了特有的操作。

image-20210115230137796

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

在要求使用基类型对象的地方,可以用继承类型的对象来代替,因此可以用fstream代替iostream&,ofstream代替ostream&,ifstream代替istream&。

如果定义了一个空文件流对象,可以随后调用open来将它与文件关联起来。如果open失败,failbit会被置位。如果open成功,则open会设置流的状态使得good()返回true。

因为调用open可能会失败,所以检测是否open成功是一个好习惯。

对一个已经打开的文件流调用open会失败,并导致failbit被置位,随后的试图使用文件流的操作都会失败。要将打开的文件流关联到另外一个文件,必须先用close()关闭文件。

fstream会自动构造和析构,当一个ifstream对象被销毁时,close会自动被调用。

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

image-20210115231618727

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

每个文件流类型都定义了一个默认的文件模式,当未指定时使用默认模式:

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

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

8.3 string流

sstream头文件定义了三个类型支持内存IO:

头文件sstream中定义的类型都继承自iostream头文件中定义的类型,除了继承得来的操作外,还有特有的操作。

image-20210115235132958

使用istringstream的例子:

/*
输入文件:
morgan 201552368 862550123
drew 973550130
lee 609550132 201550175 8005550000
*/
//设置数据结构
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);
}

使用ostringstream的例子(输出有效号码到文件,打印无效号码的相关信息):

for (const auto &entry : people){
ostringstream formatted, badNums;
for(const auto &nums : emtry.phone){
if(!valid(nums)){
badNums << " " << nums;//将数的字符串形式存入badNums
}else{
//将格式化的字符串“写入”formatted
//写入操作实际上转换为string操作,向string对象添加字符
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;
}
}
<⇧>