Home C&C++函数库 c++ 语法 程序源码 Linux C库

文件的输入/输出


c++提供了以下类来执行文件的字符输出和输入:

  • ofstream:用于写入文件的流类
  • ifstream:从文件中读取的流类
  • fstream:用于读写文件的流类

这些类直接或间接地派生自istream和ostream类。我们已经使用了这些类类型的对象: cin是istream类的对象,cout是ostream类的对象。因此,我们已经使用了与文件流相关的类。 事实上,我们可以像使用cin和cout一样使用文件流,唯一的区别是我们必须将这些流与物理文件关联起来。 让我们来看一个例子:

// basic file operations
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream myfile;
  myfile.open ("example.txt");
  myfile << "Writing this to a file.\n";
  myfile.close();
  return 0;
}
[file example.txt]
Writing this to a file.

这段代码创建了一个名为example.txt的文件,并以与使用cout相同的方式将句子插入其中, 但使用的是文件流myfile。

但让我们一步一步来:

☞  打开文件



通常对这些类中的一个对象执行的第一个操作是将它与实际文件关联起来。 这个过程称为打开文件。在程序中,打开的文件由流(即其中一个类的对象;在前面的示例中, 这是myfile),并且在此流对象上执行的任何输入或输出操作都将应用于与其关联的物理文件。

为了用stream对象打开文件,我们使用它的成员函数open:

open (filename, mode);

其中filename是一个表示要打开的文件名的字符串,mode是一个可选参数,由以下标志组合而成:

ios::in 为输入操作打开。
ios::out 为输出操作打开。
ios::binary 以二进制模式打开。
ios::ate 设置初始位置在文件末尾。
如果未设置此标志,则初始位置为文件的开头。
ios::app 将内容附加到文件的当前内容末尾,所有输出操作都在文件的末尾执行。
ios::trunc 如果为输出操作而打开文件,并且它已经存在, 那么之前的内容将被删除并被新的内容替换。

所有这些标志都可以使用按位运算符OR(|)进行组合。例如,如果我们想以二进制模式打开example.bin文件来添加数据, 可以通过以下调用成员函数open来实现:

ofstream myfile;
myfile.open ("example.bin", ios::out | ios::app | ios::binary);

ofstream、ifstream和fstream类的每个open成员函数都有一个默认模式, 在没有第二个参数的情况下打开文件时使用:

默认模式参数
ofstream ios::out
ifstream ios::in
fstream ios::in | ios::out

对于ifstream和ofstream类,ios::in和ios::out分别是默认的, 即使不指定它们作为第二个参数传递给open成员函数(组合标志)。

对于fstream,只有在调用函数时没有为模式参数指定任何值时才应用默认值。 如果函数是用该参数中的任意值调用的,则默认模式将被覆盖,而不是组合。

以二进制模式打开的文件流执行输入和输出操作,与任何格式无关。 非二进制文件被称为文本文件,有些编译可能是由于某些特殊字符(如换行符和回车符) 的格式化而产生的。

由于在文件流上执行的第一个任务通常是打开文件,所以这三个类包括一个构造函数, 该构造函数自动调用open成员函数,并具有与该函数完全相同的参数。 因此,我们也可以声明前面的myfile对象,并在前面的例子中执行相同的打开操作,方法如下:

ofstream myfile ("example.bin", ios::out | ios::app | ios::binary);

在一条语句中,组合对象构造和流打开文件的两种形式都是有效的和等效的。

要检查文件流是否成功打开了文件,可以通过调用成员is_open来实现。 如果该stream对象确实与打开的文件相关联,则该成员函数返回bool值true,否则返回false:

if (myfile.is_open()) { /* ok, proceed with output */ }

☞  关闭文件



当我们完成对一个文件的输入和输出操作时,我们应该关闭它,以便操作系统得到通知, 其资源再次可用。为此,我们调用流的成员函数close。 该成员函数刷新相关缓冲区并关闭文件:

myfile.close();

一旦调用了这个成员函数,就可以重用stream对象来打开另一个文件,并且该文件可以再次被其他进程打开。

如果一个对象在与打开的文件关联时被销毁,析构函数会自动调用成员函数close。

☞  文本文件



文本文件流是那些在其打开模式中不包含ios::binary标志的流。这些文件被用来存储文本, 因此从它们输入或输出的所有值都可能被一些格式化转换, 这些转换不一定对应于它们的文本二进制值。

文本文件的写入操作与cout的操作方式相同:

// writing on a text file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream myfile ("example.txt");
  if (myfile.is_open())
  {
    myfile << "This is a line.\n";
    myfile << "This is another line.\n";
    myfile.close();
  }
  else cout << "Unable to open file";
  return 0;
}
[file example.txt]
This is a line.
This is another line.

从文件中读取数据也可以像使用cin一样执行:

// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
  string line;
  ifstream myfile ("example.txt");
  if (myfile.is_open())
  {
    while ( getline (myfile,line) )
    {
      cout << line << '\n';
    }
    myfile.close();
  }

  else cout << "Unable to open file";

  return 0;
}
This is a line.
This is another line. 

最后一个示例读取文本文件并将其内容打印到屏幕上。我们已经创建了一个while循环, 使用getline逐行读取文件。getline返回的值是对stream对象本身的引用, 当作为布尔表达式(如在这个while循环中)进行计算时,如果流准备好执行更多操作, 则为true;如果到达文件结尾或发生其他错误,则为false。

☞  检查状态标志



以下成员函数用于检查流的特定状态(它们都返回bool值):

bad()
     如果读或写操作失败,则返回true。例如,在没有打开写入情况下,我们试图写入一个文件,
     或如果我们试图写入的设备没有剩余空间。
fail()
     在与bad()相同的情况下返回true,但在发生格式错误的情况下也返回true,
     例如当我们试图读取一个整数时提取了一个字母字符。
eof()
     如果打开读取的文件已经到达末尾,则返回true。
good()
     它是最通用的状态标志:在调用前面的任何函数都会返回true的情况下,它会返回false。
     注意,good和bad并不是完全相反的(good一次检查更多的状态标志)。

成员函数clear()可用于重置状态标志。

☞  获取和放置流定位



所有的i/o流对象在内部至少有一个内部位置:

与istream一样,Ifstream保留了一个内部的get位置, 该位置是下一个输入操作中要读取的元素的位置。

Ofstream和ostream一样,保持一个内部放置位置,其中包含必须写入下一个元素的位置。

最后,fstream保留了get和put的位置,就像iostream一样。

这些内部流位置指向流中执行下一个读写操作的位置。 这些位置可以通过以下成员函数进行查看和修改:

tellg() 和 tellp()

这两个不带参数的成员函数返回一个成员类型streampos的值, 该类型表示当前的get位置(在tellg中)或put位置(在tellp中)。

seekg() 和 seekp()

这些函数允许更改获取和放置位置的位置。两个函数都被两个不同的原型重载。第一种形式是:

seekg ( position );
seekp ( position );

使用这个原型,流指针被更改为绝对位置位置(从文件开始计数)。 该参数的类型是streampos,与函数tellg和tellp返回的类型相同。

这些函数的另一种形式是:

seekg ( offset, direction );
seekp ( offset, direction );

使用这个原型,get或put位置被设置为相对于由参数 direction确定的特定点的偏移值。 Offset是streamoff.类型。而direction的类型是seekdir,这是一种枚举类型, 确定从何处计算偏移量,它可以取以下任意值:

ios::beg 从流开始位置计数的偏移量
ios::cur 从当前位置计算的偏移量
ios::end 从流的末端开始计数的偏移量

下面的例子使用我们刚刚看到的成员函数来获取文件的大小:

// obtaining file size
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos begin,end;
  ifstream myfile ("example.bin", ios::binary);
  begin = myfile.tellg();
  myfile.seekg (0, ios::end);
  end = myfile.tellg();
  myfile.close();
  cout << "size is: " << (end-begin) << " bytes.\n";
  return 0;
}
size is: 40 bytes.

注意我们对变量begin和end使用的类型:

streampos size;

Streampos是用于缓冲区和文件定位的特定类型,是file.tellg()返回的类型。 这种类型的值可以安全地从相同类型的其他值中减去, 也可以转换为大到足以包含文件大小的整数类型。

这些流定位函数使用两种特殊类型:streampos和streamoff。这些类型也被定义为流类的成员类型:

类型 成员类型 说明
streampos ios::pos_type 定义为 fpos < mbstate_t >。
它可以转换to/from streamoff,并可以加或减这些类型的值。
streamoff ios::off_type 它是一种基本整型(如int或long long)的别名。

上面的每个成员类型都是其等效的非成员的别名(它们是完全相同的类型)。 使用哪一个并不重要。成员类型更泛型,因为它们在所有流对象上都是相同的 (甚至在使用特殊字符类型的流上也是如此),但由于历史原因, 非成员类型在现有代码中被广泛使用。

☞  二进制文件



对于二进制文件,使用提取和插入操作符(<<和>>) 以及getline这样的函数读写数据是没有效率的,因为我们不需要格式化任何数据, 而且数据很可能不会在行中进行格式化。

文件流包括两个专门用于顺序读写二进制数据的成员函数:write和read。 第一个(write)是ostream的成员函数(由ofstream继承)。 read是istream的成员函数(由ifstream继承)。fstream类的对象两者都有。他们的原型:

write ( memory_block, size );
read ( memory_block, size );

其中memory_block的类型是char*(指向char的指针),并表示一个字节数组的地址, 读取的数据元素存储在该数组中,或从该数组获取要写入的数据元素。 size参数是一个整型值,指定从内存块读取或写入的字符数。

// reading an entire binary file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos size;
  char * memblock;

  ifstream file ("example.bin", ios::in|ios::binary|ios::ate);
  if (file.is_open())
  {
    size = file.tellg();
    memblock = new char [size];
    file.seekg (0, ios::beg);
    file.read (memblock, size);
    file.close();

    cout << "the entire file content is in memory";

    delete[] memblock;
  }
  else cout << "Unable to open file";
  return 0;
}
the entire file content is in memory

在本例中,整个文件被读取并存储在内存块中。让我们来看看这是如何做到的:

首先,使用ios::ate标志打开文件,这意味着get指针将被定位在文件的末尾。 这样,当调用成员tellg()时,我们将直接获得文件的大小。

一旦我们获得了文件的大小,我们就请求分配一个足够容纳整个文件的内存块:

memblock = new char[size];

在那之后,我们继续在文件的开始处设置get位置(记住, 我们在文件的末尾用这个指针打开了文件),然后我们读取整个文件,最后关闭它:

file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();

此时,我们可以使用从文件中获得的数据进行操作。但我们的程序只是宣布文件的内容在内存中, 然后结束。

☞  缓存和同步



当我们操作文件流时,它们与类型为streambuf的内部缓冲区对象相关联。 这个缓冲区对象可能代表一个充当流和物理文件之间的中介的内存块。 例如,对于ofstream,每次调用成员函数put(写入单个字符)时, 字符可能会被插入这个中间缓冲区,而不是直接写入与流相关联的物理文件。

操作系统还可以定义用于读写文件的其他缓冲层。

当缓冲区被刷新时,其中包含的所有数据都被写入物理介质(如果它是一个输出流)。 这个过程被称为同步,并在下列任何一种情况下发生:

  • 当文件关闭时:在关闭文件之前,所有尚未刷新的缓冲区被同步,
    所有挂起的数据被写入或读到物理介质。
  • 当缓冲区满时:缓冲区有一定的大小。当缓冲区满时,它将自动同步。
  • 显式地,使用操纵符:当在流上使用某些操纵符时,会发生显式的同步。
    这些操纵符是:flush和endl。
  • 显式地,使用成员函数sync():调用流的成员函数sync()会导致立即同步。
    如果流没有相关的缓冲区或在失败的情况下,该函数返回一个等于-1的int值。
    否则(如果流缓冲区已成功同步)它将返回0。
联系我们 免责声明 关于CandCplus 网站地图
>