栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

第八章 IO库

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第八章 IO库

目录

8.1 IO类

8.1.1 IO对象无拷贝或赋值

8.1.2条件状态

8.1.3管理输出缓冲

8.2 文件输入输出

8.2.1 使用文件流对象

 8.2.2文件模式

8.3 string流

8.3.1 使用istringstream

8.3.2 使用ostringstream

小结

 术语表


已知的几种IO库设施:istream,ostream,cin,cout,cerr,>>,<<,getline

8.1 IO类

为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。列如wcin,wout,werr。 宽字符版本的类型和对象与其对应的普通 char 版本的类型定义在同一个头文件中。例如,头文件fstream定义了ifstream 和 wifstream类型。

IO类型间的关系->继承机制

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

1.由于不能搓贝IO对象,因此我们也不能将形参或返回类型设置为流类型

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

8.1.2条件状态

 while循环检查>>表达式返回的流的状态。如果输入操作成功,流保持有效状态,则条件为真

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

查询流的状态

iostate类型:提供表达流的完整功能,此类型应该作为一个位集合来使用,使用方法与quizl相同

4个iostate类型的constexpr值:表示特定的位模式,表示特定类型的IO条件,可与位运算符一起使用来一次性检测或设置多个标志位

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

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

管理条件状态

流对象的rdstate成员返回一个iostate值,对应流的当前状态

setstate操作:将给定条件条件位置位,表示发生了对应错误

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

clear成员:重载成员,有一个不接受参数的版本,可清楚(复位)所有错误标志位,执行clear()后调用good会返回true;而另一个版本接受一个iostate类型的参数

//记住cin的当前状态
auto old_state = cin.rdstate();//记住cin的当前状态
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);

8.1.3管理输出缓冲

导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
1·程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
2缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
3我们可以使用操纵符如 endl(参见1.2节,第6页)来显式刷新缓冲区。
4在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
5一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
 

 刷新输出缓缓冲区

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

unitbuf操纵符

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

cout << unitbuf; Cout<<单位; //所有输出操作后都会立即刷新缓冲区
//任何输出都立即刷新,无缓冲  
cout << nounitbuf; Cout< 


 

 关联输入和输出流

cin >> ival; //导致cout的缓冲区被刷新

 两个tite重载的版本:不带参数,返回指向输出流的指针。or接受一个指向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间的正常关联

 在这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。为了彻底解开流的关联,我们传递了一个空指针。每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。 在这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了领带。为了彻底解开流的关联,我们传递了一个空指针.每个流同时最多关联到一个流,但多个流可以同时关联到同一个Ostream.

8.2 文件输入输出

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

ifstream从一个给定文件读取数据

ofstream向一个给定文件写入数据

fstream可以读写给定文件

可以对fstream,ifstream,ofstream对象调用一下操作,但不能对其他IO类型调用这些操纵

 

8.2.1 使用文件流对象

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

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

这段代码定义了一个输入流in,它被初始化为从文件读取数据,文件名由 string类型的参数ifile指定。第二条语句定义了一个输出流 out,未与任何文件关联。在新C++标准中,文件名既可以是库类型string对象,也可以是C风格字符数组(参见3.5.4节,第109页)。旧版本的标准库只允许C风格字符数组。

用fstream代替iostream&

我们在8.1节(第279页)已经提到过,在要求使用基类型对象的地方,我们可以用继承类型的对象来替代。这意味着,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应fstream(或sstream)类型来调用。也就是说,如果有一个函数接受一个 ostream&参数,我们在调用这个函数时,可以传递给它一个ofstream对象,对istream&和 ifstream也是类似的。  
 

用7.13节中的read和print函数来读写命名文件。本例中假定输入和输出文件的名字是通过传递给main函数的参数来指定的

ifstream input(argv[1]);  //打开销售记录文件
ofstream output(argv[2]); //打开输出文件
Sales_data total;         //保存销售总额的变量
if (read(input, total))   //读取第一条销售记录
{ 
	Sales_data trans;     //保存下一条销售记录的变量
	while (read(input, trans)) //读取剩余记录
	{
		if (total.isbin() == trans.isbn())  //检查isbn
			total.combine(trans);           //更新销售总额
		else {
			print(output, total) << endl;   //打印结果
			total = trans;                  //处理下一本书
		}
	}
	print(output, total) << endl;           //打印最后一本数的销售额
}
else
cerr << "No data?!" << endl;

成员函数open和close

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

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

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

if (out)    //检查open能否成功
            //open成功,就可以使用文件了

一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用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 (intput) {       //如果文件打开成功,”处理“此文件
		process(input);
	}
	else
	{
		cerr << "couldnt't open:" + string(*p);
	}
}//每个循环步input都会离开作用域,因此会被销毁

每个循环步构造一个新的名为input的ifstream对象,并打开它来读取给定的文件。像之前一样,我们检查open是否成功。如果成功,将文件传递给一个函数,该函数负责读取并处理输入数据。如果 open 失败,打印一条错误信息并继续处理下一个文件。
       因为input是while循环的局部变量,它在每个循环步中都要创建和销毁一次(参见5.4.1节,第165页)。当一个fstream对象离开其作用域时,与之关联的文件会自动关闭。在下一步循环中,input会再次被创建。

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

 8.2.2文件模式

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

 无论那种方式,都可以指定文件模式。指定文件模式有如下限制

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

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

阻止一个ofstream清空给定文件内容的方法是同时指定app模式:

//在这几条语句中,filel都被截断
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);


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

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

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

 

8.3 string流

sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像是string是一个IO流一样

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

与fstream类型类似,头文件sstream中定义的类型都继承我们已经使用过的iostream头文件中定义的类型,处理继承得来得操作,sstream中定义得类型还增加了一些成员来管理与流相关联得string

 上述操作可以对stringstream对象调用这些操作。但不能对其他IO类型调用这些操作

8.3.1 使用istringstream

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

 首先定义一个简单得类来描述输入数据

//成员默认为公有
struct PersonInfo {
	string name;
	vectorphones;
};

程序读取数据文件,创建一个PersonInfo得vector。vector中每个元素对应文件中的一条记录。在一个循环中处理输入元素,每个循环步读取一条记录,提取出一个人名和若干个电话号码

string line, word;//分别保存来自输入的一行和单词
vectorpeople;  //保存来自输入的所有记录
//运行从输入读取数据,直至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末尾
}

这里我们用getline 从标准输入读取整条记录。如果getline 调用成功,那么line中将保存着从输入文件而来的一条记录。在while 中,我们定义了一个局部PersonInfo对象,来保存当前记录中的数据。

接下来我们将一个 istringstream与刚刚读取的文本行进行绑定,这样就可以在此 istringstream 上使用输入运算符来读取当前记录中的每个元素。我们首先读取人名,随后用一个while循环读取此人的电话号码。

当读取完line中所有数据后,内层 while循环就结束了。此循环的工作方式与前面章节中读取 cin 的循环很相似,不同之处是,此循环从一个string 而不是标准输入读取数据。当string中的数据全部读出后,同样会触发“文件结束”信号,在record上的下一个输入操作会失败。

我们将刚刚处理好的 PersonInfo追加到vector中,外层while循环的一个循环步就随之结束了。外层while循环会持续执行,直至遇到cin 的文件结束标识。
 

8.3.2 使用ostringstream

由于我们不希望输出有无效电话号码的人,因此对每个人,直到验证完所有电话号码后才可以进行输出操作。但是,我们可以先将输出内容“写入”到一个内存ostrinastream 中:
 

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;
}

在此程序中,我们假定已有两个函数,valid和format,分别完成电话号码验证和改变格式的功能。程序最有趣的部分是对字符串流formatted和 badNums 的使用。我们使用标准的输出运算符(<<)向这些对象写入数据,但这些“写入”操作实际上转换为string操作,分别向formatted和l badNums中的string对象添加字符。

小结

 术语表

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/861784.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号