《C++ Primer》 第八章 IO库

《C++ Primer》 第八章 IO库

8.1 IO类

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

IO对象无拷贝或赋值

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

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

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

管理条件状态:流对象的rdstate 成员返回一个iostate 值, 对应流的当前状态。setstate 操作将给定条件位置位,表示发生了对应错误。clear 成员是一个重载的成员。clear不接受参数的版本清楚(复位) 所有错误标志位。执行clear()后,调用good后返回true。

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

带参数的clear版本接受一个iostate值,表示流的新状态。为了复位单一的条件状态位,首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。例如,将failbit和badbit复位,但保持eofbit不变。

//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

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

导致缓冲刷新的原因:

  • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
  • 可以使用操作符如endl来显式刷新缓冲区

刷新输出缓冲区:除了使用操作符endl完成换行并刷新缓冲区的工作。还有两个类似的操作符:flush和ends。flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。

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

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

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

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

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

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

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间的正常关联

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

8.2 文件输入输出

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

  • ifstream从一个给定文件读取数据
  • ofstream 向一个给定文件写入数据
  • fstream 可以读写给定文件

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

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

用fstream 代替 iostream& : 在要求使用基类对象的地方,可以用继承类型的对象来替代。即,接受一个iostream 类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。如果一个函数接受一个 ostream& 参数,我们在调用这个函数时,可以传递给它一个 ofstream 对象,对 istream& 和 ifstream 也是类似的。

成员函数 open 和 close: 定义一个空文件流对象,可以随后调用open来将它与文件关联起来:

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

if(out)				//检查open是否成功,open成功就可以使用文件了
    
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都会离开作用域,因此会被销毁

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

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

以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时都会确定文件模式:对于一个给定流,每当打开文件时,都可以改变其文件模式:

ostream 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写数据
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

使用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末尾
}

使用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
			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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1100dp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值