C++ I/O库概述

Posted the_scent_of_th_soul

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ I/O库概述相关的知识,希望对你有一定的参考价值。

平时用到的I/O操作也不多,对这块儿的整体认识还是比较生疏,简单整理一下,当作笔记。

常用的I/O库设施:

1) istream(输入流类型),提供输入操作,该类型在头文件iostream中。

2) ostream(输出流类型),提供输出操作,在头文件iostream中。

3) cin,一个istream对象,从标准输入读取数据。

4) cout,一个ostream对象,向标准输出写入数据。

5) cerr,ostream对象,通常用于输出程序错误信息,写入到标准错误。

6) >>运算符,用来从一个istream对象读取输入数据。

7) <<运算符,向一个ostream对象写入输出数据。

8) getline函数,从一个给定的istream对象读取一行数据,存入一个给定的string对象中。

设备类型和字符大小不会影响要执行的I/O操作。可以用>>读取数据,而不用管是从一个控制台窗口、一个磁盘文件,还是一个string读取。也不用管读取的数据是要存在什么类型的对象里面。这个是通过继承机制实现的(可以将派生类对象当作其基类对象来使用)。

I/O类属于不能被拷贝的类型,所以不能将形参和返回类型设置为流类型。读写一个I/O对象会改变其状态,所以传递和返回类型不能是const的。进行I/O操作的函数通常以引用的方式传递和返回流,如下:

ostream& print(ostream& os, int x)
    os << x; 
    return os;

如果流对象发生错误,其上后续的I/O操作都会失败。只有当一个流对象处于无错状态时,我们才可以从它读数据,向它写数据。代码通常应该在使用一个流之前检查它是否出于良好的状态,一个简单的确定流状态的方法是将它当成一个表达式来使用,例如:

...

int x;
while(std::cin >> x)
   ....

if(std::cout << "hello")
   ...


...

当我们发现流发生错误时,有时候我们还想知道流对象发生了什么样的错误。I/O库定义了一个与机器无关的iostate类型,提供表达流状态的完整功能。这个类型应该作为一个位集合来使用,I/O库定义了4个iostate类型的值,表示特定的状态。可以结合位运算符,检测或设置多个状态。状态如下:

badbit:表示系统级错误,如不可恢复的读写错误。一旦badbit被置位,流就无法使用了。
failbit:发生可恢复错误时,failbit被置位,如期望读取一个数字却读出一个字符等错。
eofbit:如果到达文件结束位置,eofbit会被置位,此时failbit也会被置位。
goodbit:goodbit的值位0,表示流未发生错误,在所有错误均未置位的情况下返回true。
如果badbit、failbit、eofbit任一个被置位,则检测流状态的条件(如if(cin >> x))会失败。

流对象的rdstate成员返回一个iostate值,对应流当前状态。setstate操作将给定条件置位。clear()是一个重载的成员。我们通过简单使用流对象的这些成员来说明它们的功能:

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

也可以用clear()带一个参数的重载版本,将某些状态复位,例如:

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

每个输出流都管理一个缓冲区,用来保存程序读写的数据。有了缓冲区机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,这种缓冲区机制就可以带来很大的性能提升。
缓冲区可以刷新,导致缓冲区刷新的原因有很多:

程序正常结束,作为main函数的return操作的一部分,缓冲区会刷新。
缓冲区满,刷新缓冲区后新的数据才能继续写入缓冲区。
可以使用操纵符来显式刷新缓冲区,例如:endl刷新缓冲区并完成换行,flush刷新缓冲区,ends向缓冲区插入一个空字符,然后刷新缓冲区。
使用方法,如:cout << "hi" << ends << flush << endl;
可以使用unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是unitbuf状态,因此写到cerr的内容都会立即刷新。
cout << unitbuf;      /*所有输出操作以后都会立刻刷新缓冲区*/
//任何输出都立刻刷新,无缓冲。
cout << nounitbuf;    /*回到正常的缓冲方式*/

注意,如果程序异常终止,缓冲区是不会被刷新的。当一个程序崩溃以后,它输出的数据很可能在缓冲区里等待被打印。所以,在调试一个崩溃的程序时,需要确认那些输出的数据已经刷新了。否则,会可能会花大量时间在错误的方向调试。

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

cin >> val;  /*会导致cout 缓冲区被刷新*/

交互式系统应该关联输入、输出流,因为这意味着可以让所有输出包括用户提示信息,都在读操作之前被打印出来

x.tie(&o);      /*将流x关联到输出流o*/

介绍了一些简单的I/O操作及需要注意的问题,下来我们看看文件I/O:
头文件fstream定义了三个类型来支持文件I/O:

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

可以用I/O运算符(<< 和 >>)来读写文件,这些类型的操作与我们之前使用过的对象cin和cout的操作一样。
读写一个文件时,需要定义一个文件流对象,并将对象与文件关联起来。每个文件流都定义了一个名为open的函数,它完成一些列系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。

std::string ifile;     /*在新C++标准中,文件名既可以是string对象,也可以是c风格字符数组*/
ifstream in(ifile);  /*构造一个ifstream并打开给定文件,提供了文件名,所以open自动被调用*/ 
ofstream out;        /*输出文件流对象未关联到任何文件*/

如果一个函数接受一个ostream&型参数,我们在调用这个函数时,可以传递给它一个ofstream对象。接受istream&型参数的函数可以传递给它一个ifstream对象。这是因为ofstream类继承ostream类,ifstream类继承istream类。测试代码如下:

#include<iostream>
#include<fstream>
using namespace std;
void iofiletest(ostream&os, istream& in, std::string str)
    while(in >> str)         /*将in关联的文件内容读循环取到str中*/
        os << str << " ";     /*在循环中将str中的内容写入os关联的文件中*/
    

int main(int argc,char *argv[])

    ifstream input(argv[1]);     /*定义一个ifstream对象并打开文件argv[1]*/
    ofstream output(argv[2]);    /*定义一个输出流对象并关联到文件argv[2]*/

    std::string str;           
    iofiletest(output, input, str);

    return 0;

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

ifstream in(argv[1]);   /*定义一个ifstream对象并打开给定文件(open被自动调用)*/
ofstream out;           /*输出文件流对象未与任何文件相关联*/
out.open(argv[2]);      /*打开指定文件argv[2]*/
if(out)                /*检查open是否成功*/
/*如果open成功,就可以使用文件*/
else
/*如果open失败,随后的试图对文件流对象的所有操作都会失败*/

如果一个文件流对象open成功,它就保持和对应文件的关联。如果需要将文件流对象关联到一个新的文件,必须先关闭已经关联的文件,然后再打开新的文件,结合上一个代码片:

in.close();
out.close();
in.open(argv[2]);
out.open(argv[1]);

/*然后用两个文件流对象操作新关联的文件*/

当一个fstream对象离开其作用域时,fstream被销毁,close会自动被调用,与之关联的文件也就自动被关闭。

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

in:以读方式打开,只可以对ifstream或fstream对象设定in模式。
out:以写方式打开,只可以对ofstream或fstream对象设定out模式。
app:每次写操作前均定位到文件末尾。
ate:打开文件后立即定义到文件末尾。
trunc:截断文件。
binary:以二进制方式进行I/O。

每个文件流类型都定义了一个默认的文件模式。未显式指定模式时就用默认模式。与ifstream关联的文件默认以in模式打开。与ofstream关联的文件默认以out模式打开。与fstream关联的文件默认以in和out打开。

以out模式打开文件会丢弃已有数据,只有当out也被设定了以后才可以设定trunc模式。默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。

#include<iostream>
#include<fstream>
using namespace std;
int main(int argc,char *argv[])

    /*默认模式,以输出模式打开并截断文件*/
    ofstream out(argv[1]);

    ofstream ap(argv[2], ofstream::app);  /*隐含为输出模式*/
    /*和ap的模式一样,这里是显式表达*/
    ofstream ap1(argv[2], ofstream::out | ofstream::app);

    return 0;

上述代码执行以后,文件argv[1]中的内容会被清空。因为ap和ap1都是以app模式打开文件,所以文件argv[2]中的内容不会被清空,又因为这里没有给文件追加内容,所以文件的内容不会改变。

保留被ofstream对象打开的文件中 的已有数据的唯一方法是显式指定app或in。

每次调用open时都会确定文件模式,如果程序未显式地设置就使用默认值。
设置模式方式和文件流对象定义时设置文件模式类似。

ofstream out;   /*未关联文件未指定文件打开模式*/
out.open(argv[1]);  /*模式隐含设置为输出和截断*/
out.close();        /*关闭out关联的文件*/
out.open(argv[2], ofstream::app);  /*out关联到argv[2],模式为输出和追加*/

好啦,对于I/O流就先说这么多吧。string流就留到介绍string和容器的时候再记吧。

以上是关于C++ I/O库概述的主要内容,如果未能解决你的问题,请参考以下文章

C++标准I/O库:iostream, fstream, sstringstream

C++ I/O 进阶

C++ I/O流 (输入与输出)

C++ 的 Excel 库 - XLSX I / O - 无法使其工作

Android C++系列:Linux文件IO操作

C++标准库