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

簡介

  本教程將以C++最基本的文件I/O(輸出/輸出)開始。此後,我將從更深入的方面,爲你展示一些技巧,並分析給出一些有用的函數。

  你需要對C++有一個較好的理解,否則這個教程於你而言將是陌生而毫無用處。

你的第一個程序

  首先我將給出一段代碼,接着再逐行進行解釋。我們的第一個程序將建立一個文件,並寫入一些字符:
  

#include <fstream.h>

void main() //程序從這裏開始運行
{
    ofstream SaveFile(“cpp-home.txt”);
    SaveFile << “Hello World, from www.cpp-home.com and Loobian!”;
    SaveFile.close();
}

  僅僅如此嗎?沒錯!這個程序將在當前運行目錄下建立一個名爲cpp-home.txt的文件,並向它寫入“Hello World, from www.cpp-home.com and Loobian!”。

  下面給出各行的含義:

  include…—— 你需要包含此文件以使用C++的文件輸入/輸出函數。注意:一旦包含了這個文件,你不再需要(爲了使用cout/cin)包含iostream.h,因爲fstream.h已經自動包含了它。
在這個頭文件中聲明瞭若干個類,包括ifstream,ofstream及fstream,它們都繼承自istream和ostream類。

  ofstream SaveFile(“cpp-home.txt”);

  1)ofstream即“output file stream(輸出文件流)”。它將建立一個句柄(handle),以便我們以後能以一個文件流的形式寫入文件。

  2)SaveFile—— 這是文件句柄的名字,當然,你還可以換用任何一個你想要的名稱。

  3)(“cpp-home.txt”); —— 打開名爲cpp-home.txt的文件。如果程序運行的當前目錄已經存在這樣一個文件,則它將被替換掉;萬一不存在,程序也會爲你創建一個爲文件,你不必爲此而擔心。

  現在,讓我們稍微深入一點點。首先,我要指出的是:ofstream是一個類。因此ofstream SaveFile(“cpp-home.txt”);這一語句將創建一個該類的對象;而我們在括號中所傳遞的參數實際上將傳給構造函數:在這裏我們將我們要建立的文件的名稱作爲實際參數傳遞給了該類的構造函數。當然,我們還可以傳遞其它的一些信息,不過我以後再對其進行講解。

  SaveFile << “Hello World, from www.cpp-home.com and Loobian!”;—— “<<”看起來是不是很親切?不錯,想必你已經在cout << 中見到過。這是一個預定義好的運算符。不管怎麼說,這行語句所做的,是將上面的那段文本寫入文件。正如前面所提到的,SaveFile是一個文件句柄,它關聯一個打開的流式文件。所以,我們只須輸入句柄名,再跟着輸入“<<”,然後接着寫下一串用引號括起來的文本,就可以實現對文件的寫入。如果我們想寫入的是某個變量的值而不是帶引號的文本,也只須像通常使用cout << 一樣將變量傳遞給句柄對象,像這樣:

  SaveFile << variablename;

  就可以了!

  SaveFile.close();—— 既然我們打開了一個流文件,那麼當我們用完它之後,就必須關閉它。SaveFile是ofstream類的一個對象,而該類(ofstream)有一個用於關閉文件的成員函數,即close() 函數。因此,我們只要依次輸入文件句柄名,點號和close(),就可以關閉該文件!
注意:一旦你關閉文件,在你重新打開它以前,就再不能對它進行訪問。

  以上就是一個可以寫文件的最簡單程序。的確很容易!不過,正如你即將在以後部分的教程中所看到的,還有更多的東西要學呢!
  

讀取文件

  你已經看到了應該如何寫文件。現在,當我們已經得到cpp-home.txt文件時,我們將要讀取它,並且將內容打印在屏幕上。

  首先,我要指出的是,有很多種方法可以讀取文件。以後我會向你們介紹所有的方法(就我所知的)。此刻,我先向你展示最佳的方法(我認爲的)。

  正如你已經熟悉的——我將首先給出一段程序代碼,然後,我會詳細地對它進行解釋說明:
  

#include <fstream.h>

void main() //程序從這裏開始
{
    ifstream OpenFile("cpp-home.txt");

    char ch;
    while(!OpenFile.eof())
    {
       OpenFile.get(ch);
       cout << ch;
    }
    OpenFile.close();
}

  你想必已經瞭解首行的意義所在,而剩下的部分將由我爲你解釋。

  ifstream OpenFile(“cpp-home.txt”) —— 我猜它對現在的你而言多少會熟悉些!ifstream表示“input file stream(輸入文件流)”。在前一節的程序中,出現的則是ofstream,它的意義是“output file stream(輸出文件流)”。前一節的程序是進行文件的寫操作,這就是它用“output(輸出)”來表示的原因。而本節的程序則是讀取一個文件,這就是它用“input(輸入)”來表示的原因。這一行剩下的代碼於你而言應當是熟悉的了:OpenFile是ifstream類的一個對象,它將關聯一個輸入文件流;而用引號括住的內容,就是將要打開的文件的名稱。

  請注意:這裏沒有對要打開的文件是否存在進行測試!以後我將向你指出如何進行檢測。

  char ch; —— 聲明一個字符數組(array of type char)。只是有一點要提醒你:這樣的數組(arrays)只能存儲一個ASCII字符。

  while(!OpenFile.eof()) —— 如果已經到達文件末尾,eof( )函數將返回一個非零值。因此我們所設計的這個循環將一直持續,直至我們的文件操作到達文件末尾。這樣我們就可以遍歷整個文件,以便對它進行讀取。

  OpenFile.get(ch); —— OpenFile是類ifstream的一個對象。該類聲明瞭一個名爲get( )的成員函數。只要我們擁有該對象,我們自然就可以調用這個函數。get( )函數從相應的流文件中讀出一個字符,並將其返回給變量。在本例中,get( )函數只帶一個參數——用於存儲所讀取的字符的變量。所以,調用OpenFile.get(ch)後程序將會從OpenFile流中讀取一個字符並存入變量ch中。

  注意:如果你再次調用該函數,它將讀取下一個字符,而不是原來的那一個!你過後將理解爲什麼會這樣。

  這就是我們要不斷反覆循環直至讀操作到達文件尾的原因。每循環一次,我們將讀出一個字符並將它保存在ch中。

  cout << ch; —— 顯示ch變量值,它保存了讀取得到的字符。

  File.close(); —— 我們打開了一個流式文件,就需要關閉它。使用close()函數即可將它關閉,這和前一節的一樣!

  注意:一旦你關閉了一個文件,在你重新打開它之前,你不能再對它進行訪問。

  大功告成了!我希望你能明白我的解釋。當你編譯並運行這個程序的時候,它應當會輸出:

  “Hello World, from www.cpp-home.com and Loobian!”

掌握輸入/輸出流

  在這一章裏,我會提及一些有用的函數。我將爲你演示如何打開一個可以同時進行讀、寫操作的文件;此外,我還將爲你介紹其它打開文件的方法,以及如何判斷打開操作是否成功。因此,請接着往下讀!

  到目前爲止,我已爲你所展示的只是單一的打開文件的途徑:要麼爲讀取而打開,要麼爲寫入而打開。但文件還可以以其它方式打開。迄今,你應當已經認識了下面的方法:

  ifstream OpenFile(“cpp-home.txt”);

  噢,這可不是唯一的方法!正如以前所提到的,以上的代碼創建一個類ifstream的對象,並將文件的名字傳遞給它的構造函數。但實際上,還存在有不少的重載的構造函數,它們可以接受不止一個的參數。同時,還有一個open()函數可以做同樣的事情。下面是一個以上代碼的示例,但它使用了open()函數:

  ifstream OpenFile;
  OpenFile.open(“cpp-home.txt”);

  你會問:它們之間有什麼區別嗎?哦,我曾做了不少測試,結論是沒有區別!只不過如果你要創建一個文件句柄但不想立刻給它指定一個文件名,那麼你可以使用open()函數過後進行指定。順便再給出一個要使用open()函數的例子:如果你打開一個文件,然後關閉了它,又打算用同一個文件句柄打開另一個文件,這樣一來,你將需要使用open()函數。

  考慮以下的代碼示例:
  

#include <fstream.h>

void read(ifstream &T) //pass the file stream to the function
{  
//the method to read a file, that I showed you before
    char ch;

    while(!T.eof())
    {
       T.get(ch);
       cout << ch;
    }

    cout << endl << "--------" << endl;
}

void main()
{
    ifstream T("file1.txt");
    read(T);
    T.close();

    T.open("file2.txt");
    read(T);
    T.close();
}

  據此,只要file1.txt和file2.txt並存儲了文本內容,你將看到這些內容。

  現在,該向你演示的是,文件名並不是你唯一可以向open()函數或者構造函數(其實都一樣)傳遞的參數。下面是一個函數原型:

  ifstream OpenFile(char *filename, int open_mode);

  你應當知道filename表示文件的名稱(一個字符串),而新出現的則是open_mode(打開模式)。open_mode的值用來定義以怎樣的方式打開文件。下面是打開模式的列表:

     這裏寫圖片描述

  實際上,以上的值都屬於一個枚舉類型的int常量。但爲了讓你的編程生涯不至於太痛苦,你可以像上表所見的那樣使用那些名稱。

  下面是一個關於如何使用打開模式的例子:
  

#include <fstream.h>

void main()
{
    ofstream SaveFile("file1.txt", ios::ate);

    SaveFile << "That's new!/n";

    SaveFile.close();
}

  正如你在表中所看到的:使用ios::ate將會從文件的末尾開始執行寫入。如果我沒有使用它,原來的文件內容將會被重新寫入的內容覆蓋掉。不過既然我已經使用了它,那麼我只會在原文件的末尾進行添加。所以,如果file1.txt原有的內容是這樣:

  Hi! This is test from www.cpp-home.com!

  那麼執行上面的代碼後,程序將會爲它添上“That’s new!”,因此它看起來將變成這樣:

  Hi! This is test from www.cpp-home.com!That’s new!

  假如你打算設置不止一個的打開模式標誌,只須使用OR操作符或者是 | ,像這樣:

  ios::ate | ios::binary

  我希望現在你已經明白“打開模式”是什麼意思了!

  現在,是時候向你展示一些真正有用的東西了!我敢打賭你現在還不知道應當怎樣打開一個可以同時進行讀取和寫入操作的文件!下面就是實現的方法:

  fstream File(“cpp-home.txt”,ios::in | ios::out);

  實際上,這只是一個聲明語句。我將在下面數行之後給你一個代碼示例。但此時我首先想提及一些你應當知道的內容。

  上面的代碼創建了一個名爲“File”的流式文件的句柄。如你所知,它是fstream類的一個對象。當使用fstream時,你應當指定ios::in和ios::out作爲文件的打開模式。這樣,你就可以同時對文件進行讀、寫,而無須創建新的文件句柄。噢,當然,你也可以只進行讀或者寫的操作。那樣的話,相應地你應當只使用ios::in或者只使用ios::out —— 要思考的問題是:如果你打算這麼做,爲什麼你不分別用ifstream及ofstream來實現呢?

  下面就先給出示例代碼:
  

#include <fstream.h>

void main()
{
    fstream File("test.txt",ios::in | ios::out);

    File << "Hi!"; //將“Hi!”寫入文件   
    static char str[10]; //當使用static時,數組會自動被初始化
               //即是被清空爲零


    File.seekg(ios::beg); // 回到文件首部
                  // 此函數將在後面解釋
    File >> str;
    cout << str << endl;

    File.close();
}

  OK,這兒又有一些新東西,所以我將逐行進行解釋:

  fstream File(“test.txt”, ios::in | ios::out); —— 此行創建一個fstream對象,執行時將會以讀/寫方式打開test.txt文件。這意味着你可以同時讀取文件並寫入數據。

  File << “Hi!”; —— 我打賭你已經知道它的意思了。

  static char str[10]; —— 這將創建一個容量爲10的字符數組。我猜static對你而言或者有些陌生,如果這樣就忽略它。這只不過會在創建數組的同時對其進行初始化。

  File.seekg(ios::beg); —— OK,我要讓你明白它究竟會做些什麼,因此我將以一些有點兒離題、但挺重要的內容開始我的解釋。
還記得它麼:

while(!OpenFile.eof())
{
   OpenFile.get(ch);
   cout << ch;
}

  你是不是曾經很想知道那背後真正執行了什麼操作?不管是或不是,我都將爲你解釋。這是一個while型循環,它會一直反覆,直至程序的操作到達文件的尾端。但這個循環如何知道是否已經到了文件末尾?嗯,當你讀文件的時候,會有一個類似於“內置指針(inside-pointer)”的東西,它表明你讀取(寫入也一樣)已經到了文件的哪個位置,就像記事本中的光標。而每當你調用OpenFile.get(ch)的時候,它會返回當前位置的字符,存儲在ch變量中,並將這一內置指針向前移動一個字符。因此下次該函數再被調用時,它將會返回下一個字符。而這一過程將不斷反覆,直到讀取到達文件尾。所以,讓我們回到那行代碼:函數seekg()將把內置指針定位到指定的位置(依你決定)。你可以使用:

  ios::beg —— 可將它移動到文件首端
  ios::end —— 可將它移動到文件末端

  或者,你可以設定向前或向後跳轉的字符數。例如,如果你要向定位到當前位置的5個字符以前,你應當寫:

  File.seekg(-5);

  如果你想向後跳過40個字符,則應當寫:

  File.seekg(40);

  同時,我必須指出,函數seekg()是被重載的,它也可以帶兩個參數。另一個版本是這樣子的:

  File.seekg(-5,ios::end);

  在這個例子中,你將能夠讀到文件文本的最後4個字符,因爲:

  1)你先到達了末尾(ios::end)
  2)你接着到達了末尾的前五個字符的位置(-5)
 
  爲什麼你會讀到4個字符而不是5個?噢,只須把最後一個看成是“丟掉了”,因爲文件最末端的“東西”既不是字符也不是空白符,那只是一個位置(譯註:或許ios::end所“指”的根本已經超出了文件本身的範圍,確切的說它是指向文件最後一個字符的下一個位置,有點類似STL中的各個容器的end迭代點是指向最後一個元素的下一位置。這樣設計可能是便於在循環中實現遍歷)。

  你現在可能想知道爲什麼我要使用到這個函數。呃,當我把“Hi”寫進文件之後,內置指針將被設爲指向其後面……也就是文件的末尾。因此我必須將內置指針設迴文件起始處。這就是這個函數在此處的確切用途。
File >> str; —— 這也是新鮮的玩意兒!噢,我確信這行代碼讓你想起了cin >> .實際上,它們之間有着相當的關聯。此行會從文件中讀取一個單詞,然後將它存入指定的數組變量中。

  例如,如果文件中有這樣的文本片斷:

  Hi! Do you know me?

  使用File >> str,則只會將“Hi!”輸出到str數組中。你應當已經注意到了,它實際上是將空格作爲單詞的分隔符進行讀取的。

  由於我存入文件中的只是單獨一個“Hi!”,我不需要寫一個while循環,那會花費更多的時間來寫代碼。這就是我使用此方法的原因。順便說一下,到目前爲止,我所使用的讀取文件的while循環中,程序讀文件的方式是一個字符一個字符進行讀取的。然而你也可以一個單詞一個單詞地進行讀取,像這樣:
  

char str[30]; //每個單詞的長度不能超過30個字符
while(!OpenFile.eof())
{
   OpenFile >> str;
   cout << str;
}

  你也可以一行一行地進行讀取,像這樣:

char line[100]; //每個整行將會陸續被存儲在這裏
while(!OpenFile.eof())
{
    OpenFile.getline(line,100); // 100是數組的大小
    cout << line << endl;
}

  你現在可能想知道應當使用哪種方法。嗯,我建議你使用逐行讀取的方式,或者是最初我提及的逐字符讀取的方式。而逐詞讀取的方式並非一個好的方案,因爲它不會讀出新起一行這樣的信息,所以如果你的文件中新起一行時,它將不會將那些內容新起一行進行顯示,而是加在已經打印的文本後面。而使用getline()或者get()都將會向你展現出文件的本來面目!

  現在,我將向你介紹如何檢測文件打開操作是否成功。實現上,好的方法少之又少,我將都會涉及它們。需要注意的是,出現“X”的時候,它實際可以以“o”、 “i”來代替,或者也可以什麼都不是(那將是一個fstream對象)。

  例1:最通常的作法
  

Xfstream File(“cpp-home.txt”);
if (!File)
{
    cout << “Error opening the file! Aborting…/n”;
    exit(1);
}

  例2:如果文件已經被創建,返回一個錯誤

ofstream File("unexisting.txt", ios::nocreate);

if(!File)
{
    cout << “Error opening the file! Aborting…/n”;
    exit(1);
}

  例3:使用fail()函數

ofstream File("filer.txt", ios::nocreate);

if(File.fail())
{
    cout << “Error opening the file! Aborting…/n”;
    exit(1);
}

  例3中的新出現的東西,是fail()函數。如果有任何輸入/輸出錯誤(不是在文件末尾)發生,它將返回非零值。

  我也要講一些我認爲非常重要的內容!例如,如果你已經創建一個流文件對象,但你沒有進行打開文件操作,像這樣:

  ifstream File; //也可以是一個ofstream

  這樣,我們就擁有一個文件句柄,但我們仍然沒有打開文件。如果你打算遲些打開它,那麼可以用open()函數來實現,我已經在本教程中將它介紹了。但如果在你的程序的某處,你可能需要知道當前的句柄是否關聯了一個已經打開的文件,那麼你可以用is_open()來進行檢測。如果文件沒有打開,它將返回0 (false);如果文件已經打開,它將返回1 (true)。例如:

  ofstream File1;
  File1.open(“file1.txt”);
  cout << File1.is_open() << endl;

  上面的代碼將會返回1(譯註:指File1.is_open()函數,下句同),因爲我們已經打開了一個文件(在第二行)。而下面的代碼則會返回0,這是由於我們沒有打開文件,而只是創建了一個流文件句柄:

  ofstream File1;
  cout << File1.is_open() << endl;

檢測輸入/輸出的狀態標誌

  在此我不打算解釋“標誌(flags)”一詞的含義,不過假如你真的完全不理解關於這方面的概念,那麼將本章讀過一遍之後也許你對此會得到一些認識,我也相信你同樣能理解這部分的理論。儘管如此,如果你還是不明白標誌在C++中的含義,我推薦你閱讀一些關於這個主題的資料。
好,讓我們開始吧。

  C++中負責的輸入/輸出的系統包括了關於每一個輸入/輸出操作的結果的記錄信息。這些當前的狀態信息被包含在io_state類型的對象中。io_state是一個枚舉類型(就像open_mode一樣),以下便是它包含的值(譯註:表中第一列爲枚舉值的名稱,第二列爲該值相應含義的描述):

     這裏寫圖片描述
  
  有兩種方法可以獲得輸入/輸出的狀態信息。一種方法是通過調用rdstate()函數,它將返回當前狀態的錯誤標記(上表中提到的)。例如,假如沒有任何錯誤,則rdstate()會返回goodbit.
另一種方法則是使用下面任何一個函數來檢測相應的輸入/輸出狀態:

  bool bad();
  bool eof(); //還記得它麼?“不斷讀取文件內容直到到達文件末尾!”
  bool fail(); //噢,這也是老朋友……檢測一個打開操作是否成功
  bool good();

  假如badbit標誌被標設(譯註:原文爲“If the badbit flag is up”,這裏將“is up”譯爲“標設”,意即出現了badbit對應的錯誤,badbit狀態被置爲當前的錯誤狀態,下同),則bad()函數返回true;假如failbit標誌被標設,則fail()函數返回true;假如沒有錯誤發生(goodbit標誌被標設),則good()函數返回true;假如操作已經到達了文件末尾(eofbit被標設),則eof()函數返回true.

  如果錯誤發生,你必須清除這些錯誤狀態,以使你的程序能正確適當地繼續運行——如果你這麼打算的話。要清除錯誤狀態,需使用clear()函數。此函數帶一個參數,它是你將要設爲當前狀態的標誌值。假使你想讓你的程序“清清爽爽”地運行下去,只要將ios::goodbit作爲實參。你將在以下內容中看到示例代碼。

  我將向你展示示例代碼,以鞏固你所學到的理論知識。

  示例1:簡單的狀態檢測
  

//實際應用中可將 FileStream替換成你相應在使用的文件流句柄
if(FileStream.rdstate() == ios::eofbit)
    cout << "End of file!/n";
if(FileStream.rdstate() == ios::badbit)
    cout << "Fatal I/O error!/n";
if(FileStream.rdstate() == ios::failbit)
    cout << "Non-fatal I/O error!/n";
if(FileStream.rdstate() == ios::goodbit)
    cout << "No errors!/n";

  示例2:clear()函數

#include <fstream.h>

void main()
{
    ofstream File1("file2.txt"); //建立file2.txt
    File1.close();

    //下面的檢測代碼將會返回錯誤,這是因爲我使用了ios::noreplace打開模式
    //它模式在試圖打開一個已存在的文件時會返回錯誤
    ofstream Test("file2.txt",ios::noreplace);

    //上一行將導致ios::failbit錯誤,我們這就將其演示出來
    if(Test.rdstate() == ios::failbit)
        cout << "Error...!/n";

    Test.clear(ios::goodbit); //將當前狀態重置爲ios::goodbit

    if(Test.rdstate() == ios::goodbit) //檢測程序是否已經正確地施行了設置
        cout << "Fine!/n";

    Test.clear(ios::eofbit); //將狀態標誌設爲ios::eofbit. 無實際用途.

    if(Test.rdstate() == ios::eofbit) //檢測是否已經正確地施行了設置       
        cout << "EOF!/n";

    Test.close();

}

  除了使用標記值判斷,你也可以使用函數(譯註:指bad()、eof()、fail()、good()這些函數)的形式進行判斷,兩者實際上是一樣的——都是檢測某個標記是否被標設。這些函數前面已經介紹過,我就不再重複了。如果你對如何使用它們還不是十分確定,那就重新回顧一下本教程中我曾經爲你演示的應該如何檢測一個文件打開操作是否成功的那部分內容。在那裏我就使用了fail()函數。

二進制文件的處理

  雖然有規則格式(formatted)的文本(到目前爲止我所討論的所有文件形式)非常有用,但有時候你需要用到無格式(unformatted)的文件——二進制文件。它們和你的可執行程序看起來一樣,而與使用<<及>>操作符創建的文件則大不相同。get()函數與put()函數則賦予你讀/寫無規則格式文件的能力:要讀取一個字節,你可以使用get()函數;要寫入一個字節,則使用put()函數。你應當回想起get()——我曾經使用過它。你可能會疑惑爲什麼當時我們使用它時,輸出到屏幕的文件內容看起來是文本格式的?嗯,我猜這是因爲我此前使用了<<及>>操作符。

  譯註:作者的所謂“規則格式文本(formatted text)”即我們平時所說的文本格式,而與之相對的“無格式文件(unformatted files)”即以存儲各類數據或可執行代碼的非文本格式文件。通常後者需要讀入內存,在二進制層次進行解析,而前者則可以直接由預定好的<<及>>操作符進行讀入/寫出(當然,對後者也可以通過恰當地重載<<及>>操作符實現同樣的功能,但這已經不是本系列的討論範圍了)。

  get()函數與都各帶一個參數:一個char型變量(譯註:指get()函數)或一個字符(譯註:指put()函數,當然此字符也可以以char型變量提供)。

  假如你要讀/寫一整塊的數據,那麼你可以使用read()和write()函數。它們的原型如下:

  istream &read(char *buf, streamsize num);
  ostream &write(const char *buf, streamsize num);

  對於read()函數,buf應當是一個字符數組,由文件讀出的數據將被保存在這兒。對於write()函數,buf是一個字符數組,它用以存放你要寫入文件的數據。對於這兩個函數,num是一個數字,它指定你要從文件中讀取/寫入的字節數。

  假如在讀取數據時,在你讀取“num”個字節之前就已經到達了文件的末尾,那麼你可以通過調用gcount()函數來了解實際所讀出的字節數。此函數會返回最後一次進行的對無格式文件的讀入操作所實際讀取的字節數。

  在給出示例代碼之前,我要補充的是,如果你要以二進制方式對文件進行讀/寫,那麼你應當將ios::binary作爲打開模式加入到文件打開的參數表中。

  現在就讓我向你展示示例代碼,你會看到它是如何運作的。
  
  示例1:使用get( )和put( )


#include <fstream.h>

void main()
{
    fstream File("test_file.txt",ios::out | ios::in | ios::binary);

    char ch;
    ch='o';

    File.put(ch); //將ch的內容寫入文件

    File.seekg(ios::beg); //定位至文件首部

    File.get(ch); //讀出一個字符

    cout << ch << endl; //將其顯示在屏幕上

    File.close();
}

  示例2:使用read( )和write( )
  

#include <fstream.h>
#include <string.h>

void main()
{
    fstream File("test_file.txt",ios::out | ios::in | ios::binary);

    char arr[13];
    strcpy(arr,"Hello World!"); //將Hello World!存入數組

    File.write(arr,5); //將前5個字符——"Hello"寫入文件

    File.seekg(ios::beg); //定位至文件首部

    static char read_array[10]; //在此我將打算讀出些數據

    File.read(read_array,3); //讀出前三個字符——"Hel"

    cout << read_array << endl; //將它們輸出  

    File.close();
}

  tellg() ——返回一個int型數值,它表示“內置指針”的當前位置。此函數僅當你在讀取一個文件時有效。例如:
  

#include <fstream.h>

void main()
{
    //假如我們已經在test_file.txt中存有了“Hello”的內容
    ifstream File("test_file.txt");

    char arr[10];

    File.read(arr,10);

    //由於Hello佔5個字符,因此這裏將返回5
    cout << File.tellg() << endl; 

    File.close(); 
}

  tellp() —— 與tellg()有同樣的功能,但它用於寫文件時。總而言之:當我們讀取一個文件,並要知道內置指針的當前位置時,應該使用tellg();當我們寫入一個文件,並要知道內置指針的當前位置時,應該使用tellp(). 由於此函數的用法與tellg()完全一樣,我就不給出示例代碼了。

  seekp() —— 還記得seekg()麼?當我在讀取一個文件,並想到達文件中某個特定位置時,就曾使用過它。seekp()亦如此,只不過它用於寫入一個文件的時候。例如,假如我在進行文件讀寫,而要定位到當前位置的三個字符之前,則需調用FileHandle.seekg(-3). 但如果我是在寫入一個文件,並且比如我要重寫後5個字符的內容,我就必須往回跳轉5個字符,因而,我應該使用FileHandle.seekp(-5) .

  ignore() —— 使用於讀取文件之時。如果你想略過一定數量的字符,只需使用此函數。實際上,你也可以使用seekg()來代替,然而使用ignore()有一個優點——你可以指定一個特定“界限規則(delimiter rule)”,同樣使得ignore()在指定的位置停下。函數原型如下:

  istream& ignore( int nCount, delimiter );

  nCount表示要略過的字符數量,而delimiter —— 與它的名稱有着同樣的含義:假如你想在文件末尾停下,則可使用EOF值傳入,這樣一來此函數就等同於seekg();但該參數還可以使用其他值,例如‘/n’這樣可以在換行的同時定位在新行處。下面是示例:
  

#include <fstream.h>

void main()
{
    //假設test_file.txt中已經存有"Hello World"這一內容
    ifstream File("test_file.txt");

    static char arr[10];

    //假如一直沒有遇到字符"l",則向前定位直到跳過6個字符
    //而如果期間遇到"l",則停止向前,定位在該處
    File.ignore(6,'l'); 

    File.read(arr,10);

    cout << arr << endl; //它將顯示"lo World!"

    File.close();

}

  getline() —— 雖然前面的章節中我曾提到過這個函數,但還有一些內容我們未曾涉及:此函數不但可用於逐行讀取,而且它還可以設爲遇到某個特定字符後停止讀取。下面給出傳遞這一參數的方法:

  getline(array,array_size,delim);

  以下爲示例代碼:
  

#include <fstream.h>

void main()
{
    //假設test_file.txt中已經存有"Hello World"這一內容
    ifstream File("test_file.txt");

    static char arr[10];

    /*讀取,直到滿足下面的條件之一:
    1)已經讀取10個字符
    2)遇到字母"o"
    3)出現新一行
    */
    File.getline(arr,10,'o');

    cout << arr << endl; //將顯示"Hell"
    File.close();
    }

  peek() —— 此函數將返回輸入流文件的下一個字符,但它不移動內置指針。我想你該記得,像get()這樣的函數也返回輸入流文件的下一個字符,而與此同時它將移動內置指針。所以當你再次調用get()函數的時候,它會返回再下一個字符,而非前面那個。哦,使用peek()也會返回字符,但它不會移動“光標”。所以,假如你連續兩次調用peek()函數,它會返回同一個字符。考慮以下代碼:
  

#include <fstream.h>

void main()
{
    //假設test_file.txt中已經存有"Hello World"這一內容
    ifstream File("test_file.txt");

    char ch;

    File.get(ch);
    cout << ch << endl; //將顯示"H"

    cout <<    char(File.peek()) << endl; //將顯示"e"
    cout <<    char(File.peek()) << endl; //將再次顯示"e"

    File.get(ch);
    cout << ch << endl; //還是顯示"e"

    File.close();

}

  順便說一下,我忘了講——peek()函數實質上返回的是字符的ASCII碼,而非字符本身。因此,假如你想看到字符本身,你得像我在示例中做的那樣進行調用(譯註:即要轉爲char類型)。

  _unlink() —— 刪除一個文件。假如你要使用此函數,需要在你的程序中包含io.h頭文件。下面是示例代碼:
  

#include <fstream.h>
#include <io.h>

void main()
{
    ofstream File;

    File.open("delete_test.txt"); //創建一個文件
    File.close();

    _unlink("delete_test.txt"); //刪除這個文件

    //試圖打開此文件,但假如它已不存在
    //函數將返回一個ios::failbit錯誤值
    File.open("delete_test.txt",ios::nocreate);

    //驗證它是否返回該值
    if(File.rdstate() == ios::failbit)
        cout << "Error...!/n"; //耶,成功了

    File.close();

}

  putback() —— 此函數將返回最後一個所讀取字符,同時將內置指針移動-1個字符。換言之,如果你使用get()來讀取一個字符後再使用putback(),它將爲你返回同一個字符,然而同時會將內置指針移動-1個字符,所以你再次使用get()時,它還是會爲你返回同樣的字符。下面是示例代碼:
  

#include <fstream.h>

void main()
{
    //test_file.txt應包含內容"Hello World"
    ifstream File("test_file.txt");

    char ch;

    File.get(ch);

    cout << ch << endl; //將顯示"H"

    File.putback(ch);
    cout << ch << endl; //仍將顯示"H"

    File.get(ch);
    cout << ch << endl; //再一次顯示"H"

    File.close();
}

  flush() —— 在處理輸出流文件的時候,你所存入的數據實際上並非立刻寫入文件,而是先放入一個緩衝區中,直到該緩衝區放滿數據之後,這些數據才被存入真正的文件中(在你的磁盤上)。旋即緩衝區會被清空,再重新進行下一輪寫入。

  但假如你想在緩衝區寫滿之前就將其中的數據寫入磁盤,則使用flush()函數。只須像這樣進行調用:FileHandle.flush(),這樣緩衝區內的數據將會寫入實際的物理文件,而後緩衝區被清空。

  再補充一點(高階的)內容:flush()函數會調用與相應流緩衝(streambuf)相聯繫的sync()函數(出自MSDN)。

結語

  嗯,我希望你現在可以實現你的文件輸入/輸出程序了。我已經將自己所知的內容都涉及到,我想它們比你所需要的還要多。即使如此,還是會有一些內容我沒有提及的……不過我想你或許都不會使用到那些方面的內容。因此,如果你還需要了解更多關於某些特定主題的高階的理論,可以在互聯網上搜索,例如,可以嘗試一下google.com,但你可別來問我!我不對任何詢問我關於如何實現某個程序此類問題的郵件作答覆。

  假如你喜歡這個系列的教程,或者相反,我都會非常高興能看到你的意見。所以請隨意地在網上聯繫我:[email protected]

  想獲取更多的C++教程,請訪問:www.cpp-home.com

發佈了183 篇原創文章 · 獲贊 418 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章