文件I/O

来源:互联网 发布:silverlight 图片源码 编辑:程序博客网 时间:2024/05/05 22:48
fstream 头文件定义了三种支持文件 IO 的类型:

1、ifstream,由 istream 派生而来,提供读文件的功能。

2、ofstream,由 ostream 派生而来,提供写文件的功能。

3、fstream,由 iostream 派生而来,提供读写同一个文件的功能。

这些类型都由相应的 iostream 类型派生而来,这个事实意味着我们已经知道使用 fstream 类型需要了解的大部分内容了。特别是,可使用 IO 操作符(<< 和 >> )在文件上实现格式化的 IO,而且在前面章节介绍的条件状态也同样适用于 fstream 对象。

fstream 类型除了继承下来的行为外,还定义了两个自己的新操作—— open 和 close,以及形参为要打开的文件名的构造函数。fstream、ifstream 或 ofstream 对象可调用这些操作,而其他的 IO 类型则不能调用。

迄今为止,我们的程序已经使用过标准库定义的对象:cin、cout 和 cerr。需要读写文件时,则必须定义自己的对象,并将它们绑定在需要的文件上。假设 ifile 和 ofile 是存储希望读写的文件名的 strings 对象,可如下编写代码:

  // construct an ifstream and bind it to the file named ifile
  ifstream infile(ifile.c_str());
  // ofstream output file object to write file named ofile
  ofstream outfile(ofile.c_str());

上述代码定义并打开了一对 fstream 对象。infile 是读的流,而 outfile 则是写的流。为 ifstream 或者 ofstream 对象提供文件名作为初始化式,就相当于打开了特定的文件。

  ifstream infile; // unbound input file stream
  ofstream outfile; // unbound output file stream

上述语句将 infile 定义为读文件的流对象,将 outfile 定义为写文件的对象。这两个对象都没有捆绑具体的文件。在使用 fstream 对象之前,还必须使这些对象捆绑要读写的文件:

  infile.open("in"); // open file named "in" in the current directory
  outfile.open("out"); // open file named "out" in the current directory

调用 open 成员函数将已存在的 fstream 对象与特定文件绑定。为了实现读写,需要将指定的文件打开并定位,open 函数完成系统指定的所有需要的操作。


打开文件后,通常要检验打开是否成功,这是一个好习惯:

  // check that the open succeeded
  if (!infile) {
  cerr << "error: unable to open input file: "
  << ifile << endl;
  return -1;
  }

这个条件与之前测试 cin 是否到达文件尾或遇到某些其他错误的条件类似。检查流等效于检查对象是否“适合”输入或输出。如果打开(open)失败,则说明 fstream 对象还没有为 IO 做好准备。当测试对象

  if (outfile) // ok to use outfile?

返回 true 意味着文件已经可以使用。由于希望知道文件是否未准备好,则对返回值取反来检查流:

  if (!outfile) // not ok to use outfile?


fstream 对象一旦打开,就保持与指定的文件相关联。如果要把 fstream 对象与另一个不同的文件关联,则必须先关闭(close)现在的文件,然后打开(open)另一个文件:要点是在尝试打开新文件之前,必须先关闭当前的文件流。open 函数会检查流是否已经打开。如果已经打开,则设置内部状态,以指出发生了错误。接下来使用文件流的任何尝试都会失败。

  ifstream infile("in"); // opens file named "in" for reading
  infile.close(); // closes "in"
  infile.open("next"); // opens file named "next" for reading




考虑这样的程序,它有一个 vector 对象,包含一些要打开并读取的文件名,程序要对每个文件中存储的单词做一些处理。假设该 vector 对象命名为 files,程序也许会有如下循环:

  // for each file in the vector
  while (it != files.end()) {
  ifstream input(it->c_str()); // open the file;
  // if the file is ok, read and "process" the input
  if (!input)
  break; // error: bail out!
  while(input >> s) // do the work on this file
  process(s);
  ++it; // increment iterator to get next file
  }

每一次循环都构造了名为 input 的 ifstream 对象,打开并读取指定的文件。构造函数的初始化式使用了箭头操作符(第 5.6 节)对 it 进行解引用,从而获取 it 当前表示的 string 对象的 c_str 成员。文件由构造函数打开,并假设打开成功,读取文件直到到达文件结束符或者出现其他的错误条件为止。在这个点上,input 处于错误状态。任何读 input 的尝试都会失败。因为 input 是 while 循环的局部变量,在每次迭代中创建。这就意味着它在每次循环中都以干净的状态即 input.good() 为 true,开始使用。

如果希望避免在每次 while 循环过程中创建新流对象,可将 input 的定义移到 while 之前。这点小小的改动意味着必须更仔细地管理流的状态。如果遇到文件结束符或其他错误,将设置流的内部状态,以便之后不允许再对该流做读写操作。关闭流并不能改变流对象的内部状态。如果最后的读写操作失败了,对象的状态将保持为错误模式,直到执行 clear 操作重新恢复流的状态为止。调用 clear 后,就像重新创建了该对象一样。

如果打算重用已存在的流对象,那么 while 循环必须在每次循环进记得关闭(close)和清空(clear)文件流:

  ifstream input;
  vector<string>::const_iterator it = files.begin();
  // for each file in the vector
  while (it != files.end()) {
  input.open(it->c_str()); // open the file
  // if the file is ok, read and "process" the input
  if (!input)
  break; // error: bail out!
  while(input >> s) // do the work on this file
  process(s);
  input.close(); // close file when we're done with it
  input.clear(); // reset state to ok
  ++it; // increment iterator to get next file
  }

如果忽略 clear 的调用,则循环只能读入第一个文件。要了解其原因,就需要考虑在循环中发生了什么:首先打开指定的文件。假设打开成功,则读取文件直到文件结束或者出现其他错误条件为止。在这个点上,input 处于错误状态。如果在关闭(close)该流前没有调用 clear 清除流的状态,接着在 input 上做的任何输入运算都会失败。一旦关闭该文件,再打开 下一个文件时,在内层 while 循环上读 input 仍然会失败——毕竟最后一次对流的读操作到达了文件结束符,事实上该文件结束符对应的是另一个与本文件无关的其他文件。

如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用 clear 清除该流的状态。
 



在打开文件时,无论是调用 open 还是以文件名作为流初始化的一部分,都需指定文件模式(file mode)。每个 fstream 类都定义了一组表示不同模式的值,用于指定流打开的不同模式。与条件状态标志一样,文件模式也是整型常量,在打开指定文件时,可用位操作符(第 5.3 节)设置一个或多个模式。文件流构造函数和 open 函数都提供了默认实参(第 7.4.1 节)设置文件模式。默认值因流类型的不同而不同。此外,还可以显式地以模式打开文件。表 8.3 列出了文件模式及其含义。

File Modes

in     打开文件做读操作
 
out    打开文件做写操作
 
app    在每次写之前找到文件尾
 
ate    打开文件后立即将文件定位在文件尾
 
trunc  打开文件时清空已存在的文件流
 
binary 以二进制模式进行 IO 操作

out、trunc 和 app 模式只能用于指定与 ofstream 或 fstream 对象关联的文件;in 模式只能用于指定与 ifstream 或 fstream 对象关联的文件。所有的文件都可以用 ate 或 binary 模式打开。ate 模式只在打开时有效:文件打开后将定位在文件尾。以 binary 模式打开的流则将文件以字节序列的形式处理,而不解释流中的字符。

默认时,与 ifstream 流对象关联的文件将以 in 模式打开,该模式允许文件做读的操作:与 ofstream 关联的文件则以 out 模式打开,使文件可写。以 out 模式打开的文件会被清空:丢弃该文件存储的所有数据。

从效果来看,为 ofstream 对象指定 out 模式等效于同时指定了 out 和 trunc 模式。

对于用 ofstream 打开的文件,要保存文件中存在的数据,唯一方法是显式地指定 app 模式打开:

  // output mode by default; truncates file named "file1"
  ofstream outfile("file1");
  // equivalent effect: "file1" is explicitly truncated
  ofstream outfile2("file1", ofstream::out | ofstream::trunc);
  // append mode; adds new data at end of existing file named "file2"
  ofstream appfile("file2", ofstream::app);

outfile2 的定义使用了按位或操作符(第 5.3 节)将相应的文件同时以 out 和 trunc 模式打开。