文件輸入輸出(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.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章