C++中的文件输入/输出

  • 非常详细的一篇关于C++中的文件输入/输出的文章,适合看自己需要的部分,或者当作查询资料;不必要看懂所有细节

文件的输入/输出

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

  • ofstream:  写入文件的Stream类
  • ifstream: 从文件中读取的Stream类
  • fstream: 包含读和写的Stream类


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

// 基本文件操作
#include 
#include 
using namespace std;

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

 

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

让我们一步一步来:

打开文件

通常对这些类中的一个对象执行的第一个操作是将其关联到一个真正的文件。此过程称为打开文件。一个打开的文件在一个程序中由一个流表示(例如,这些类中的一个对象; 在前面的例子中,这是 myfile) ,对这个流对象执行的任何输入或输出操作都将应用到与它相关联的物理文件。

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

open (filename, mode);


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

ios::in 打开输入操作
ios::out 打开输出操作
ios::binary 以二进制模式打开
ios::ate

设置文件末尾的初始位置。

如果未设置此标志,则初始位置为文件的开头。

ios::app 所有输出操作都在文件末尾执行,将内容追加(appending)到文件的当前内容。
ios::trunc 如果文件已经为输出操作打开并且已经存在,则删除其以前的内容并用新的内容替换。


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

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

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

class default mode parameter
ofstream ios::out
ifstream ios::in
fstream ios::in | ios::out
  • 对于 ifstream 和 ofstream 类,ios: : in 和 ios: : out 分别被自动假定,即使不传递给 open 成员函数第二个参数(它们的模式)。
  • 对于 fstream,只有在调用函数时没有为 mode 参数指定任何值时才应用默认值。如果使用该参数中的任何值调用该函数,则将重写默认模式。
  • 以二进制模式打开的文件流独立于任何格式考虑执行输入和输出操作。非二进制文件称为文本文件,可能发生某些翻译,如某些特殊字符(如换行符和回车符)的格式。


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

ofstream myfile ("example.bin", ios::out | ios::app | ios::binary);
  •  在单个语句中结合构造函数和流打开文件。两种打开文件形式都是有效的和等价的。


要检查文件流是否成功打开了文件,可以通过调用成员函数 is _ open 来完成。如果流对象确实与打开的文件关联,则此成员函数返回一个 bool 值 true,否则返回 false:

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

 

关闭文件

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

myfile.close();
  • 调用此成员函数后,可以重用流对象打开另一个文件,并且该文件可以再次由其他进程打开。
  • 如果某个对象在仍与打开的文件关联的情况下被销毁,析构函数将自动调用成员函数 close。

 

文本文件

文本文件(Text file)流是那些在其开放模式中未包含ios::binary标志的文件流。这些文件旨在存储文本,因此输入或输出到这些文件的所有值都会经历一些格式转换,这些格式转换不一定对应于它们的文本二进制值。

对文本文件的写操作的执行方式与使用 cout 时相同:

// writing on a text file
#include 
#include 
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;
}

执行后:

// 会生成 example.txt 文件, 内容是:

This is a line.
This is another line.

 

从文件中读取数据也可以按照与 cin 相同的方式执行: 

// reading a text file
#include 
#include 
#include 
using namespace std;

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

  else cout << "Unable to open file"; 

  return 0;
}

 输出结果:

This is a line.
This is another line.

 

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

检查状态标志

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

bad():

  • 如果读写操作失败,则返回 true。例如,在我们尝试写入一个没有打开以便写入的文件的情况下,或者我们尝试写入的设备没有空间了。

fail():

  • 在 bad ()的相同情况下返回 true,但在发生格式错误的情况下也是如此,比如当我们试图读取一个整数数字时提取了一个字母字符。

eof():

  • 如果打开供读取的文件已到结尾,则返回 true。

good():

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

获取(get)和放置(put)流定位

所有 i/o 流对象都保持至少一个内部位置:

  • ifstream 与 istream 一样,保持一个内部 get 位置,其中包含在下一个输入操作中要读取的元素的位置。
  • ofstream 和 ostream 一样,保持一个内部的 put 位置,其中包含下一个元素必须写入的位置。
  • 最后,fstream 同时保留 get 和 put 位置,就像 iostream 一样。


这些内部流位置指向流中执行下一个读写操作的位置。可以使用下列成员函数观察和修改这些位置:

tellg() 和 tellp():

这两个没有参数的成员函数返回一个成员类型streampos,这是一个表示当前 get位置(在 tellg 情况下)或 put位置(在 tellp 情况下)的类型。

seekg() 和 seekp():

这些函数允许更改 get 和 put 位置的定位。这两个函数都用两个不同的原型重载。第一种形式是:
seekg ( position );
seekp ( position );

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

这些函数的另一种形式是:
seekg ( offset, direction );
seekp ( offset, direction );

使用这个原型,get 或 put 位置被设置为相对于由参数方向确定的某个特定点的偏移值(offset value)。偏移量(offset)为是streamoff类型。方向(direction)是 Seekdir 类型,它是一个枚举类型,用于确定从哪里开始计算偏移,并且可以采用以下任何值: 

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


下面的示例使用刚才的成员函数来获取文件的大小:

// obtaining file size
#include 
#include 
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. // 假设 example.bin 是一个 40 byte的文件

 

注意我们用于变量 start 和 end 的类型:

streampos size;

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

这些流定位函数使用两种特殊的类型: streamposstreamoff。这些成员类型同样被定义在stream类中:

Type Member type Description
streampos ios::pos_type Defined as fpos.
It can be converted to/from streamoff and can be added or subtracted values of these types.
streamoff ios::off_type It is an alias of one of the fundamental integral types (such as int or 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 参数是一个整数值,指定要 读取/写入 内存块的字符数。

// 读取整个二进制文件
#include 
#include 
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;
}

在这个例子中,整个文件被读取并存储在一个内存块中:

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

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

memblock = new char[size];

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

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

此时,我们可以使用从文件中获得的数据进行操作了...

 

缓冲与同步

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

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

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

  • 关闭文件时: 在关闭文件之前,将同步所有尚未刷新的缓冲区,并将所有等待的数据(pending data)写入或读取到物理介质。
  • 当缓冲区满时: 缓冲区有一定的大小。当缓冲区满时,它会自动同步。
  • 显式地使用操作器: 当某些操作器用于流时,将发生显式的同步。这些操纵器分别是:  flush 和 endl.。
  • 显式地,使用成员函数 sync () : 调用流的成员函数 sync ()会导致立即同步。如果流没有关联的缓冲区,或者出现故障,则此函数返回一个等于 -1的 int 值。否则(如果流缓冲区成功同步)返回0。

你可能感兴趣的:(cpp,c++,开发语言)