C++中的檔案輸入/輸出(3):掌握輸入/輸出流 (轉)

amyz發表於2007-11-13
C++中的檔案輸入/輸出(3):掌握輸入/輸出流 (轉)[@more@]

C++中的輸入/輸出(3):namespace prefix = o ns = "urn:schemas--com::office" />

原作:Ilia Yordanov,  to:loobian@cpp-home.com">loobian@cpp-home.com

 

掌握輸入/輸出流

在這一章裡,我會提及一些有用的。我將為你演示如何開啟一個可以同時進行讀、寫操作的檔案;此外,我還將為你介紹其它開啟檔案的方法,以及如何判斷開啟操作是否成功。因此,請接著往下讀!

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

 

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

 

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

 

ifstream OpenFile;

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

 

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

考慮以下的程式碼示例:

#include

 

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的值用來定義以怎樣的方式開啟檔案。下面是開啟模式的列表:

名稱

描述

ios::in

開啟一個可讀取檔案

ios::out

開啟一個可寫入檔案

ios::app

你寫入的所有資料將被追加到檔案的末尾,此方式使用ios::out

ios::ate

你寫入的所有資料將被追加到檔案的末尾,此方式不使用ios::out

ios::trunk

刪除檔案原來已存在的內容(清空檔案)

ios::nocreate

如果要開啟的檔案並不存在,那麼以此引數open()函式將無法進行。

ios::noreplace

如果要開啟的檔案已存在,試圖用open()函式開啟時將返回一個錯誤。

ios::binary

以二進位制的形式開啟一個檔案。

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

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

 

#include

 

void main()

{

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

 

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

 

  SaveFile.close();

}

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

Hi! This is test from !

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

Hi! This is test from 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

 

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型迴圈,它會一直反覆,直至程式的操作到達檔案的尾端。但這個迴圈如何知道是否已經到了檔案末尾?嗯,當你讀檔案的時候,會有一個類似於“內建指標(ins-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;

好啦,這一章講得夠多啦。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982205/,如需轉載,請註明出處,否則將追究法律責任。

相關文章