[C++学习]IO库

第II部分 C++标准库

标准库的核心是很多容器类和一族泛型算法,这些设施能帮助我们编写简洁高效的程序,标准库会去关注那些薄记操作的细节,特别是内存管理,这样我们的程序就可以将全部注意力投入到需要求解的问题上了。

第8章 IO库

C++语言不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等。还有一些类型允许内存IO,即,从string读取数据,向string写入数据。

8.1 IO类

IO库类型和头文件

头文件				类型
iostream 		  istream、wistream从流读取数据
				  ostream、wostream向流写入数据
				  iostream、wiostream读写流
fstream			  ifstream、wifstream从文件读取数据
				  ofstream、wofstream向文件写入数据
				  fstream、wfstream读写文件
sstream			  istringstream、wistringstream从string读取数据
				  ostringstream、wostringstream向string写入数据
				  stringstream、wstringstream读写string

为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。例如:wcin、wcout和wcerr是分别对应cin、cout和cerr的宽字符版对象。宽字符版本的类型和对象与其对应的普通char版本的类型定义在同一个头文件中。

IO类型间的关系

概念上,设备类型和字符串大小都不会影响我们要执行的IO操作。例如,我们可以用>>读取数据,而不用管是从一个控制台窗口,一个磁盘文件,还是一个string读取。类似的,我们也不用管读取的字符能存入一个char对象内,还是需要一个wchar_t对象来存储。

标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制(inheritance)实现的。利用模板,我们可以使用具有继承关系的类,而不必了解继承机制如何工作的细节。

类型ifstream和istringstream都继承自istream。因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象。也就是说,我们是如何使用cin的,就可以同样地使用这些类型的对象。例如,可以对一个ifstream或istringstream对象调用getline,也可以使用>>从一个ifstream或istringstream对象中读取数据。类似的,类型ofstream和ostringstream都继承自ostream。因此,我们都是如何使用cout的,就可以同样地使用这些类型的对象。

8.1.1 IO对象无拷贝或赋值

我们不能拷贝或对IO对象赋值

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

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

8.1.2 条件状态

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

IO库条件状态

strm::iostatestrm是一种IO类型。iostate是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbitstrm::badbit用来指出流已崩溃
strm::failbitstrm::failbit用来指出一个IO操作失败了
strm::eofbitstrm::eofbit用来指出流到达了文件结束
strm::goodbitstrm::goodbit用来指出流未处于错误状态。此值保证为0
s.eof()若流s的eofbit置位,则返回true
s.fail()若流s的failbit或badbit置位,则返回true
s.bad()若流s的biabit置位,则返回true
s.good()若流s处于有效状态,则返回true
s.clear()将流s中所有条件状态位复位,将流的状态设置为有效,返回void
s.clear(flags)根据给定的flag标志位,将流s中对应条件状态位复位。flags的类型为strm::iostate,返回void
s.setstate(flags)根据给定的flag标志位,将流s中对应条件状态位置位。flags的类型为strm::iostate。返回void
s.rdstate()返回流s的当前条件状态,返回值类型为strm::iostate

一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态,确定一个流对象的状态的最简单方法是将它当作一个条件来使用:

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

查询流的状态

IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用。IO库定义了4个iostate类型的constexpr值,表示特定的位模式,这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标志位。

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

标准库还定义了一组函数来查询这些标志位的状态。操作good在所有错误位均未置位的情况下返回true,而bad、fail和eof则在对应错误位时返回true。此外,在badbit被置位时,fail也会返回true。这意味着,使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和bad操作只能表示特定错误。

管理条件状态

流对象的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版本将接受一个iostream值,表示流的原有状态。为了复位单一的条件状态位,我们首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);	// 置位failbit、badbit

8.1.3 管理输出缓冲

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

刷新输出缓冲区

endl、flush、ends

cout << "hi!" << endl;		// 换行刷新
cout << "hi!" << flush;		// 刷新
cout << "hi!" << ends;		// 空字符刷新

unitbuf操纵符

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

cout << unitbuf;
cout << nounitbuf;

在这里插入图片描述

关联输入和输出流

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

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

我们既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream:

ostream *old_tie = cin.tie(nullptr);	// old_tie指向当前关联到cin的流(如果有的话),cin不再与其它流关联

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

8.2 文件输入输出

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

fstream特有的操作

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

8.2.1 使用文件流对象

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

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

用fstream代替iostream&

接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。

成员函数open和close

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

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

如果调用open失败,failbit会被置位。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯:

if(out)		// 检查open是否成功
    		// open成功,我们可以使用文件了

一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用open会失败,并会导致failbit被置位。随后的试图使用文件流的操作都会失败。为了将文件关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,我们可以打开新的文件:

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

自动构造和析构

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会自动调用

8.2.2 文件模式

文件模式

in以读方式打开
out以写方式打开
app每次写操作前均定位到文件末尾
ate打开文件后立即定位到文件末尾
trunc截断文件
binary以二进制方式进行IO
  • 只可以对ofstream或fstream对象设定out模式。
  • 只可以对ifstream或fstream对象设置in模式。
  • 只有当out也被设定时才可设定trunc模式。
  • 只有要runc没被设定,就可以设定app模式。在app模式下,即使没有显示指定out模式,文件也总是以输出方式被打开。
  • 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加到文件末尾;或者同时指定in模式,即打开文件的同时进行读写操作。
  • ate和binary模式可以用于任何类型的文件流对象,且可以与其它文件模式组合使用。

以out模式打开文件会丢弃已有数据

// 截断
ofstream out("file1");
ofstream ou12("file1", ofstream::out);
ofstream out3("file1", ofstream::out | ofstream::trunc);
// 保留
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();

8.3 string 流

sstream头文件定义了三个类型来支持内存IO,istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可以从string读数据也可以向string写数据。

stringstream特有的操作

sstream strm;		strm是一个未绑定的stringstream对象,sstream是头文件sstream定义					  的一个类型。
sstream strm(s);	strm是一个sstream对象,保存string s的一个拷贝。此构造函数是							explicit的。
strm.str()			返回strm所保存的string的拷贝
strm.str(s)			将string s拷贝到strm中。返回void

8.3.1 使用istringstream

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

string line, word;
vector<PersonInfo> people;
while(getline(cin, line)){
    PersonInfo info;
    istringstream record(line);		// 读取行转换成istringstream
    record >> info.name;
    while(record >> word)			// 从istringstream逐词读取
        info.phones.push_back(word);
    people.push_back(info);
}

8.3.2 使用ostringstream

for(const auto &entry : people){
    ostringstream formatted, badNums;
    for(const auto &nums : entry.phones){
        if(!valid(nums)){
            badNums << " " << nums;
        }else{
            formatted << " " << format(nums);
        }
        if(badNums.str().empty()){
            os << entry.name << " " << formatted.str() << endl;
        }{
            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、付费专栏及课程。

余额充值