第8章:IO库

  • #1.IO类
    • IO对象无拷贝或赋值
    • 条件状态
    • 管理输出缓冲
  • #2.文件输入输出
    • 使用文件流对象
    • 文件模式
  • #3.string流
    • 使用istringstream
    • 使用ostringstream

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

#1. IO类

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

1.1 IO对象无拷贝或赋值

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

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

1.2 条件状态

IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。表中列出了IO类定义了一些函数和标志,可以帮助我们访问和操作流的条件状态

函数 状态
strm::iostate strm是一种IO类型。iostate 是一种机器相关类型,提供了表达条件状态的完整功能
strm::badbit strm::badbit 用来指出流已崩溃
strm::failbit strm::failbit 用来指出一个IO操作失败了
strm::eofbit strm::eofbit 用来指出流到达了文件结束
strm::goodbit strm::goodbit 用来指出流未处于错误状态。此值保证为零。
查询流的状态

将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们需要知道流为什么失败。

IO库提供了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。IO库定义了4个iostate类型的constexpr值表示特定的位模式。badbit表示系统级错误,如不可恢复的读写错误。在发生可恢复错误后,failbit被置位。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果bidbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。

管理条件状态

流对象的rdstate成员返回一个iostate值,对应流的当前状态。setState操作将给定条件位置位,表示发生了对应错误。

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

1.3 管理输出缓冲

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

os << "Please enter a value";

文本可能立即打印出来,也可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。

导致缓冲刷新的原因有很多:

  • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲区,而后新的数据才能写入缓冲区。
  • 我们可以使用操纵符如endl来显示刷新缓冲区。
  • 在每个输出操作之后,我们可以使用操纵符unitbuf设置流的内部状态,来清空缓冲区。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
刷新输出缓冲区

我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。

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

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

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

==如果程序崩溃,输出缓冲区不会被刷新。==

关联输入和输出流

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

cin >> ival;

导致cout缓冲区被刷新。

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


#2 文件输入输出

头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。

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

fstream特有的操作 含义
fstream fstrm; 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型。
fstream fstrm(s); 创建一个fstream,并打开名为s的文件。s可以是string类型,或者是一个指向C风格字符串的指针。这些构造函数都是explict的。默认的文件模式依赖于fstream的类型
fstream fstrm(s,mode); 与前一个构造函数类似,但按指定mode打开文件。
fstrm.open(s) 打开名为s的文件,并将文件与fstrm绑定。返回void。
fstrm.close() 关闭与fstrm绑定的文件。返回void。
fstrm.is_open() 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭。

2.1 使用文件流对象

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

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

接受一个iostream类型引用参数的函数,可以用一个对应的fstream类型来调用。

成员函数open和close

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

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

如果调用open失败,failbit会被置位。

自动构造和析构
for(auto p = argv + 1;p != argv + argc;++p) {
    ifstream input(*p); //创建输入流并打开文件
    if(input) { //如果文件打开成功,“处理”此文件
        process(input);
    }else {
        cerr << "couldn't open: " << string(*p); 
    }
} //每个循环步input都会离开作用域,因此会被销毁。

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

2.2 文件模式

每个流都有一个关联的文件模式,用来指出如何使用文件。表给出了文件模式和它们的定义:

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

无论用哪种方式打开文件,我们都可以指定文件模式,指定文件模式有如下限制:

  • 只可以对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模式打开。

以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打开文件中已有数据的唯一方法是显示指定app或in模式。==

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

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

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

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

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


#3. string流

istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可从string读取数据也可向string写数据。

stringstream特有的操作 含义
sstream strm; strm是一个未绑定的stringstream对象。
strm.str() 返回strm所保存的string的拷贝
strm.str(s) 将string s拷贝到strm中。返回void

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

3.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 numbers " << badNums.str() << endl;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 8.1 IO类 IO类继承机制:ifstream和istringstream继承自istream,ofstream...
    咸鱼翻身ing阅读 219评论 0 0
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,166评论 0 9
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,362评论 0 4
  • 概述 java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。java.io ...
    Steven1997阅读 9,178评论 1 25
  • 1 C++缓冲区 在学习标准IO库之前,我们先了解C++中缓冲区的使用。关于操作系统中缓冲区的学习与理解,请查看操...
    saviochen阅读 876评论 0 4