c++标准I/O总结
之前我们总结了Unix系统I/O和Unix标准I/O,这次我们就顺便总结一下C++I/O库,主要内容都来自C++ primer, 这也算是对C++ primer一个复习吧.
-
IO类
IO库类型和头文件标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制实现的.简单来说, 继承机制使我们可以声明一个特定的类继承自另一个类.我们通常可以将一个派生类(继承类)对象当做其基类(所继承的类)对象来使用.
类型ifstream和istringstream都继承自istream.因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象.例如,我们可以对一个ifstream或istringstream对象调用getline, 也可以使用>>从一个ifstream或istringstream对象中读取数据.类似的,ofstream和ostringstream都继承自ostream.同理,我们是如果使用cout的,就可以同样的使用这些类型的对象.-
IO对象无拷贝或赋值
这是初学者非常容易犯的一个错误,经常在将IO对象作为函数参数时忘记加上引用符号,导致错误却摸不着头脑..
另外,读写一个IO对象会改变其状态,所以传递和返回的引用也不能是const的. -
条件状态
image.png
一旦一个流发生错误,其上后续的IO操作都会失败.只有当一个流处于无错状态,我们才能对其进行读写.确定一个流对象的状态的最简单的方法是将其作为一个条件来使用.
while(cin>>word)
//ok: 操作成功while循环检查>>表达式返回的流的状态.如果输入操作成功,流保持有效状态,则条件为真.
badbit表示系统级错误,如不可恢复的读写错误.通常情况下,如果badbit被设置了,流就无法使用了.在发生可恢复错误之后,failbit被置位,如期望读取数值却读出一个字符等错误,这种问题通常是可以修正的,流还可以继续使用.如果到达文件尾端,eofbit和failbit都会被置位.goobit的值为0,表示流未发生错误.
标准库还定义了一组函数来查询或设置这些标志位的状态, 这些函数在上面的图中已经有了,我也不再赘述.
-
管理输出缓冲
我们在前面总结Unix标准I/O的时候就已经知道了,这些非系统I/O都人为增加缓冲区来提高I/O效率,C++标准I/O也不例外.导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
- 程序正常结束,作为main函数return的一部分.
- 缓冲区满时,要刷新缓冲,新的数据才能写入.
- 可以使用操纵符如endl来显式刷新缓冲区.
- 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区.默认情况下,对cerr就是设置unitbuf的.
- 一个输出流可能会被关联到另一个流,这种情况下,当读写被关联的流时,关联到流的缓冲区会被刷新.例如,默认情况下,cin和cerr都关联到cout,所以,读cin或者写cerr都会导致cout的缓冲区被刷新.
刷新输出缓冲区
cout<<"hi"<<endl; //输出hi和一个换行,然后刷新缓冲区
cout<<"hi"<<flush; //输出hi,然后刷新缓冲区
cout<<"hi"<<ends; //输出hi和一个空字符,然后刷新缓冲区unitbuf操纵符
cout<<unitbuf; //所有输出操作后都会立刻刷新缓冲区
cout<<nounitbuf; //回到正常的缓冲方式警告:如果程序崩溃,输出缓冲区是不会被刷新的.因此我们的数据很有可能停留在输出缓冲区中等待打印.所以,给我们的教训是:当调试一个崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了,否则可能数据已经输出,但是停留在缓冲区内没有打印出来.
关联输入和输出流
标准库将cout和cin关联在一起,因此读cin会导致cout的缓冲区被刷新.注:交互式系统通常应该关联输入流和输出流.这就意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来.tie函数有两个重载的版本:一个版本不带参数,返回指向输出流的指针.如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针,tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream.
-
-
文件输入输出
fstream.h头文件定义了三个支持文件的类型:ifstream用来读文件,ofstream用来写入文件,fstream用来读写文件.这些类型提供了与我们之前用在cin和cout上相同的操作.另外,我们也可以使用IO操作符(>>和<<)来读写文件,同样的我们也可以将他们从基类其他继承过来的IO操作用在这三种类型上.
image.png
由于这三种类型是与文件打交道的,所以他们也有一些特有的文件IO操作,这些操作并不能被其他IO类型使用.如下图所示:
文件模式
image.png
每个流都有一个关联的文件模式,用来指出如何使用文件.下表列出了文件模式和它们的含义.
无论用哪种方式来打开文件,我们都可以指定文件模式,调用open打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以.指定文件模式有以下限制:
- 只可以对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模式打开.
-
string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样.这三个类型分别是istringstream,ostringstream和stringstream,用法可以类比三个文件IO类型,不再细说.另外是stringstream特有的操作.如下表所示:
image.pngistringstream
当我们的工作是处理某一行的单个单词时,istringstream就显得十分的重要了.举个小例子(来自c++ primer):
假设一个文件,列出了一些人和他们的电话号码,有些人可能有多个电话号码.文件看起来如下:
jack 123456 154679
lucy 789453 493716
tony 456712 791846
文件格式都是如此.我们首先定义一个结构体来描述输入数据:
struct PersonInfo{ strting name; vector<string> phone; }
再写如下程序来处理文件:
string line, word; // 分别保存来自输入的一行和单词 vector<PersionInfo> people;//保存来自输入的所有记录 //逐行从输入读取数据,直至cin遇到文件尾(或其他错误) while(getline(cin,line)) { Persioninfo info;//创建一个保存此记录数据的对象 istringstream record(line);//将记录绑定到刚读入的行 record>>info.name;//读取名字 while(record >> word)//读取电话号码 info.phones.push_back(word);//保存到容器中 people.push_back(info);//将此记录追加到people末尾 }
代码并不难理解,也有注释,我就不在这里赘述了.
ostringstream
对于ostringstream来说,用法和istringstream是有类似之处的,我们可以使用<<运算符来将数据写入ostringstream对象.例如:int a = 1234;
ostringstream out;
out<<a<<"hello";
cout<<out.str<<endl;这段代码就会打印
1234hello
.用法不难,用处比较专一,也很有用.
结语:写到这里,C++的标准IO总结就差不多了,这篇文章写的挺早,不过中间出去比赛,一直就没怎么动,到了今天才把完全写完.