文件输入输出(Input/Output with files)

Input/Output with files

Published by Juan Soulie

Last update on May 17, 2006 at 1:32am

C++提供了以下几个类来进行字符方式的文件输入输出:

  • ofstream: 文件输出的Stream
  • ifstream: 文件输入的Stream
  • fstream: 文件输入输出的Stream

这几个类是直接或者间接从istream ostream类派生的。我们已经使用过这两种类型的一些对象:cinistream的一个对象,而coutostream的对象。所以,我们已经有了使用和文件流(file streams)相关的类的经验。实际上,我们能够像使用我们已经习惯了的cincout的同样的方式使用文件流,唯一的区别是,我们必须把文件流和物理文件关联起来。看看下面的例子:

// 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替代了cout

我们一步一步来:

打开一个文件

通常来说,这些stream类的对象的头一个操作就是和一个真实文件相关联。这个操作被称为打开文件。在程序中一个stream对象(指某个stream类的一个实例,前面例子中的stream对象就是myfile)表征一个打开的文件,任何对这个stream对象的输入输出操作都会反应到与之相关联的物理文件上。

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

open (filename, mode);

其中filename是一个以null结尾的const char *型字符串(和string相同的类型),指明要被打开的文件名;是选项参数,mode可以是下表里标志的组合:

ios::in

Open for input operations.

ios::out

Open for output operations.

ios::binary

Open in binary mode.

ios::ate

Set the initial position at the end of the file.
If this flag is not set to any value, the initial position is the beginning of the file.

ios::app

All output operations are performed at the end of the file, appending the content to the current content of the file. This flag can only be used in streams open for output-only operations.

ios::trunc

If the file opened for output operations already existed before, its previous content is deleted and replaced by the new one.

这些标志应该用位或操作符(|)来组合。比如,如果我们想用二进制方式打开文件example.bin并且向其中追加数据,我们应该这么使用函数open()

ofstream myfile;

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

ofstreamifstreamfstream这些类的open()函数都有自己的默认打开标志,用以缺省第二个参数打开文件:

class

default mode parameter

ofstream

ios::out

ifstream

ios::in

fstream

ios::in | ios::out

对于ifstream类和ofstream类来说,ios::inios::out是分别自动假定的,哪怕在mode参数并没有包含它们的情况下,也会传递给open()函数。

默认值仅仅是在函数调用时缺省mode参数的情况下才会生效。如果函数调用时指定了mode参数,默认参数会被覆盖,而不是于指定的参数组合。

以二进制方式打开的文件流的输入输出过程和文件的格式无关。文本文件可以用非二进制方式打开,这样在格式化字符的时候,会有一些转义的操作发生(比如换行和回车)。

因为操作文件流的第一项任务通常就是打开一个文件,所以这三个类都提供了自动调用open()函数的构造函数,使用与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)这个文件以便使其资源重新可用。关闭文件使用close()成员函数。这个函数不需要参数,它所做的事就是置空相关联的缓冲区并且关闭打开的文件:

 

myfile.close();

这个函数调用之后,这个stream对象可以用来打开其他的文件,而以前打开的文件则可以重新被其他的处理过程打开。

如果一个stream对象在析构的时候仍然打开着一个文件,析构函数将自动调用close()成员函数来关闭文件。

文本文件

我们不包含ios::binary标志打开文件的时候将创建文本文件流。文本文件设计为储存文本,所以在输入活着输出的时候会遇到格式转换(formatting transformations),而并不是保持它们二进制语义。

文本文件上的数据输出操作和使用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.

文本文件上的数据输出操作和使用cout的操作也是一样的:

// 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 (! myfile.eof() )

    {

      getline (myfile,line);

      cout << line << endl;

    }

    myfile.close();

  }

 

  else cout << "Unable to open file";

 

  return 0;

}

This is a line.

This is another line. 

上面这个例子读入一个文本文件然后将它的内容输出到屏幕上。注意我们是如何使用成员函数eof()的,它在文件搜索倒文件末尾的时候返回true。我们创建了一个while循环,当返回true(比如,读完了整个文件之后)的时候结束循环。

检测状态标志

除了eof()用以检测是否到达文件末尾以外,还有一些成员函数可以用来检测一个流对象的其他状态(都以bool型作为返回值):

bad()

Returns true if a reading or writing operation fails. For example in the case that we try to write to a file that is not open for writing or if the device where we try to write has no space left.

fail()

Returns true in the same cases as bad(), but also in the case that a format error happens, like when an alphabetical character is extracted when we are trying to read an integer number.

eof()

Returns true if a file open for reading has reached the end.

good()

It is the most generic state flag: it returns false in the same cases in which calling any of the previous functions would return true.

为了重置调用上述这些成员函数后所设定的状态标志,我们应该调用clear()函数,调用它不需要带参数。

getput流指针

所有的输入/输出流对象都至少拥有一个内部的流指针:

Ifstreamistream类似,有一个被称作get pointer的指针指向下一个输入操作将要读入的元素。

Ofstreamostream类似,有一个被称作put pointer的指针指向下一个输出操作将要写入的位置。

最后, fstreamiostream(它是从istreamostream派生而来)继承了get pointerput pointer两者。

通过下列的成员函数来操作这些指向读入或者写入位置的内部流指针:

tellg()tellp()

这两个函数没有参数,返回一个成员类型pos_type的值,pos_type是一个整数类型,代表当前get stream pointer的位置(如果是tellg)或put stream pointer的位置(如果是tellp)。

seekg()seekp()

这两个函数允许我们改变getput流指针的位置。它们都有两个不同的原型,第一个原型是:

seekg ( position );
seekp ( position );

使用这一对原型,流指针将会被设置为position指定的绝对位置(从文件起始开始的位置)。参数的类型和tellgtellp的返回值一样:成员类型pos_type,一个整型的值。

第二种原型是:

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

使用这一对原型,Using this prototype, the position of the get or put pointer is set to an offset value relative to some specific point determined by the parameter direction. offset is of the member type off_type, which is also an integer type. And direction is of type seekdir, which is an enumerated type (enum) that determines the point from where offset is counted from, and that can take any of the following values:

ios::beg

offset counted from the beginning of the stream

ios::cur

offset counted from the current position of the stream pointer

ios::end

offset counted from the end of the stream

The following example uses the member functions we have just seen to obtain the size of a file:

// obtaining file size

#include <iostream>

#include <fstream>

using namespace std;

 

int main () {

  long begin,end;

  ifstream myfile ("example.txt");

  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.

Binary files

In binary files, to input and output data with the extraction and insertion operators (<< and >>) and functions like getline is not efficient, since we do not need to format any data, and data may not use the separation codes used by text files to separate elements (like space, newline, etc...).

File streams include two member functions specifically designed to input and output binary data sequentially: write and read. The first one (write) is a member function of ostream inherited by ofstream. And read is a member function of istream that is inherited by ifstream. Objects of class fstream have both members. Their prototypes are:

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

Where memory_block is of type "pointer to char" (char*), and represents the address of an array of bytes where the read data elements are stored or from where the data elements to be written are taken. The size parameter is an integer value that specifies the number of characters to be read or written from/to the memory block.

// reading a complete binary file

#include <iostream>

#include <fstream>

using namespace std;

 

ifstream::pos_type size;

char * memblock;

 

int main () {

  ifstream file ("example.txt", 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 complete file content is in memory";

 

    delete[] memblock;

  }

  else cout << "Unable to open file";

  return 0;

}

the complete file content is in memory

In this example the entire file is read and stored in a memory block. Let's examine how this is done:

First, the file is open with the ios::ate flag, which means that the get pointer will be positioned at the end of the file. This way, when we call to member tellg(), we will directly obtain the size of the file. Notice the type we have used to declare variable size:

ifstream::pos_type size;

ifstream::pos_type is a specific type used for buffer and file positioning and is the type returned by file.tellg(). This type is defined as an integer type, therefore we can conduct on it the same operations we conduct on any other integer value, and can safely be converted to another integer type large enough to contain the size of the file. For a file with a size under 2GB we could use int:

int size;

size = (int) file.tellg();

Once we have obtained the size of the file, we request the allocation of a memory block large enough to hold the entire file:

memblock = new char[size];

Right after that, we proceed to set the get pointer at the beginning of the file (remember that we opened the file with this pointer at the end), then read the entire file, and finally close it:

file.seekg (0, ios::beg);

file.read (memblock, size);

file.close();

At this point we could operate with the data obtained from the file. Our program simply announces that the content of the file is in memory and then terminates.

Buffers and Synchronization

When we operate with file streams, these are associated to an internal buffer of type streambuf. This buffer is a memory block that acts as an intermediary between the stream and the physical file. For example, with an ofstream, each time the member function put (which writes a single character) is called, the character is not written directly to the physical file with which the stream is associated. Instead of that, the character is inserted in that stream's intermediate buffer.

When the buffer is flushed, all the data contained in it is written to the physical medium (if it is an output stream) or simply freed (if it is an input stream). This process is called synchronization and takes place under any of the following circumstances:

  • When the file is closed: before closing a file all buffers that have not yet been flushed are synchronized and all pending data is written or read to the physical medium.
  • When the buffer is full: Buffers have a certain size. When the buffer is full it is automatically synchronized.
  • Explicitly, with manipulators: When certain manipulators are used on streams, an explicit synchronization takes place. These manipulators are: flush and endl.
  • Explicitly, with member function sync(): Calling stream's member function sync(), which takes no parameters, causes an immediate synchronization. This function returns an int value equal to -1 if the stream has no associated buffer or in case of failure. Otherwise (if the stream buffer was successfully synchronized) it returns 0.

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页