平时用到的I/O操作也不多,对这块儿的整体认识还是比较生疏,简单整理一下,当作笔记。
常用的I/O库设施:
设备类型和字符大小不会影响要执行的I/O操作。可以用>>读取数据,而不用管是从一个控制台窗口、一个磁盘文件,还是一个string读取。也不用管读取的数据是要存在什么类型的对象里面。这个是通过继承机制实现的(可以将派生类对象当作其基类对象来使用)。
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
#include
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
#include
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]中的内容不会被清空,又因为这里没有给文件追加内容,所以文件的内容不会改变。
每次调用open时都会确定文件模式,如果程序未显式地设置就使用默认值。
设置模式方式和文件流对象定义时设置文件模式类似。
ofstream out; /*未关联文件未指定文件打开模式*/
out.open(argv[1]); /*模式隐含设置为输出和截断*/
out.close(); /*关闭out关联的文件*/
out.open(argv[2], ofstream::app); /*out关联到argv[2],模式为输出和追加*/
好啦,对于I/O流就先说这么多吧。string流就留到介绍string和容器的时候再记吧。