程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 【C++探索之旅】第一部分第十課之文件讀寫,海闊憑魚躍

【C++探索之旅】第一部分第十課之文件讀寫,海闊憑魚躍

編輯:關於C++

\

 


 

內容簡介

1、第一部分第十課:文件讀寫,海闊憑魚躍

2、第一部分第十一課預告:小練習,猜單詞

 


文件讀寫,海闊憑魚躍

 

上一課《【C++探索之旅】第一部分第九課:數組威武,動靜合一》中,我們學習了動態數組和靜態數組,也看到其實字符串很類似字符數組(到了之後的第二部分,學習面向對象,我們會知道其實string是一個類)。

 

到目前為止,我們寫的程序還比較簡單,當然了,因為我們剛開始學習C++嘛。但只要加以訓練,我們就慢慢地能夠寫一些真正的應用了。我們也開始逐漸了解C++的基礎知識了,不過缺了很重要的一環:與文件交互。

 

我們已經學會如何將信息輸出到控制台(console)以及如何提取用戶在控制台中輸入的數據(使用cin和cout)。但是,我們豈能就此罷休。想想我們之前介紹過的一些程序,例如:記事本,一些IDE(VS, CodeBlocks, xCode, Eclipse, etc),繪圖軟件,等等,都能夠讀寫文件。

 

在游戲領域就更是如此啦(我知道一幫宅男已經激動了):游戲裡的數據要保存,游戲的圖片,音樂,道具,等等。都需要存檔。

 

總之,如果一個軟件不會與文件交互,那麼它的功能是比較有限的。

 

因此,一起來學習如何讀寫文件吧。你會發現,如果你掌握了cin和cout的用法,那其實你已經知道大半啦。

 


寫入文件

 

我們要讀寫文件,首先需要打開文件。就好像平時我們要記筆記一樣,你總得先打開筆記本吧,才能閱讀內容,或者往裡面寫東西。

 

一旦文件被打開之後,接下來的操作就很類似之前用cin和cout來進行標准輸入和輸出了。我們又會與老朋友<<和>>見面。

 

用術語來說,我們會將一個程序和外界的通信方式用"流"來描述。流,英語是stream。記得嗎?我們要使用cin和cout,需要用

 

#include 

 

因為cin和cout定義在iostream這個C++的標准庫中。而這裡的iostream就是input output stream的縮寫,表示"輸入輸出流"。所以,其實我們早就在不知不覺地接觸流的概念了。

 

在這一章中,我們要和文件交互,那麼就需要文件流來幫忙了。聰明如你一定想到了,是的,文件的英語是file,那麼文件流就是file stream。是不是很簡單呢?

 

因此,我們需要用到fstream這個標准庫,fstream就是file stream的縮寫。

 

當然了,如果你不是參加一個程序員的派對,也不需要顯得很專業,那麼說"讀寫文件"就可以了。

 

fstream頭文件

 

在C++中,我們要使用一個功能,需要引入合適的頭文件。因此,我們在程序一開始須要這樣做:

 

#include 

 

接下來,我們就學習如何創建一個文件流,以便我們能讀寫文件。

 

以寫模式打開文件

 

流其實是對象,還記得我們說C++是一門面向對象的語言嗎?當然我們現在還不深究,要到第二部分講面向對象編程時才會暢聊類和對象。暫時只需要知道這些流其實都是C++的對象(不是找對象的對象,少年你想多了)。

 

也完全無需害怕,因為我們之後還會不斷提到流。暫時,只需把其看作比較高級的變量就可以了。這些文件流包含了文件的很多信息,提供給我們很多功能,例如可以關閉文件,在文件中移動,等等。

 

你會看到,聲明一個流的對象,其實就和我們聲明變量一樣簡單。首先,我們來看看如何創建用於寫文件的流,須要用到ofstream,也就是output file stream,因為是從程序向文件輸入數據,因此對於程序來說是"出去"的流,因此是output(輸出),而不是input(輸入)。

 

話休絮煩。翠花,上"栗子":

 

#include 
#include 
using namespace std;

int main()
{ 
    ofstream myStream("C:/Cpp/Files/scores.txt"); 
    //聲明用於寫入文件的流,
    //此文件是在C盤下的Cpp文件夾的子文件夾Files中的scores.txt文件
    
    ?return 0;
}

 

在上面程序中,我在myStream後面的括號中指定了文件的路徑.這個路徑可以有兩種形式:

 

絕對路徑:就是文件的所在,不過是從根文件夾開始的路徑。例如:C:/Cpp/Files/scores.txt

相對路徑:也是文件的所在,不過是相對於你的程序的路徑。例如,你的程序位於C:/Cpp/,那麼如果你的文件是在C:/Cpp/Files/scores.txt,你在程序裡指定文件的相對路徑時就要寫 Files/scores.txt

 

自此,我們就可以使用這個文件流來寫文件啦。

 

如果文件不存在,那麼會被自動創建。不過,至少指定的目錄要存在,不然會出現"目錄不存在"的錯誤。在我們上面的例子中,至少目錄C:/Cpp/Files必須事先存在。

 

在打開文件的時候,也會有其他問題。例如文件不屬於你,或者磁盤已滿,等等,總之,打開失敗。因此,我們為了保險起見,總要測試文件是否順利被打開。我們使用 if (myStream) 的方法來測試。

 

ofstream myStream("C:/Cpp/Files/scores.txt"); //試著打開這個文件

if(myStream) //測試打開文件是否成功
{
 //一切順利,我們可以使用此文件了
}
else
{
 cout << "出錯: 無法打開此文件." << endl;
}

 

至此,我們已經做好了寫文件的准備工作。你會看到,接下來的操作還是有點眼熟的。

 

向流中寫入數據

 

前面我們說過寫入文件的操作就和以前我們使用cout類似。因此當我對你說要使用<<運算符來進行操作的時候,你應該不會太驚訝。

 

#include 
#include 
#include 
using namespace std;

int main()
{
 string const fileName("C:/Cpp/Files/scores.txt");
 ofstream myStream(fileName.c_str());
 
 if(myStream) 
 {
 myStream << "大家好,我是被寫入文件的一句話." << endl;
 myStream << 54.26 << endl;
 int age(23);
 myStream << "我" << age << "歲了." << endl;
 }
 else
 {
 cout << "出錯: 無法打開此文件." << endl;
 }
 
 return 0;
}

上面的程序中,可以看到我們首先聲明了一個string的變量,裡面存放了C:/Cpp/Files/scores.txt這個字符串,不過之後在將其賦給ofstream的對象myStream時,我們卻用了c_str()這個函數,這是為什麼呢?

 

其實,ofstream接受的參數是char *(暫時不需要知道是什麼,馬上我們會學習指針的知識,到時就清楚了),c_str()函數就是用於將string轉換成char *

 

運行此程序,不出意外的話,你的電腦的C:/Cpp/Files/目錄下就會多出一個文件 scores.txt, 裡面的內容如下所示:

 

\

 

你也來試試

 

你也可以寫一個程序,請求用戶輸入自己的名字和年齡,然後你的程序將這些信息寫入文件。

 

文件的不同打開模式

 

我們只需要再處理一個小問題:

 

假如文件已經存在,那怎麼辦呢?

 

如果運行上面的已有程序,那麼文件的內容會被刪除,然後替換為你寫入的內容。但是假如我們想要保留文件本來的內容,只是想在文件末尾追加我們的新內容呢?

 

不用怕,肯定有辦法的。只需要在打開文件的時候添加第二個參數,用於指明文件的打開模式,如下所示:

 

ofstream myStream("C:/Cpp/Files/scores.txt", ios::app);

 

app是英語append的縮寫,表示"追加",也就是說寫入的內容不會覆蓋原本文件裡的內容,而是追加到文件末尾。

 


讀取文件

 

我們學習了如何寫文件,現在來學習如何讀取文件內容吧。你會看到,兩種操作是很類似的。

 

以讀的形式打開文件

 

之前我們用了ofstream的對象,那麼這次就要用到ifstream的對象了,ifstream是input file stream的縮寫。當然也需要測試文件是否順利被打開。

 

ifstream myStream("C:/Cpp/Files/scores.txt"); //試著打開文件

if(myStream)
{
 //可以讀取文件
}
else
{
 cout << "出錯: 無法以讀的形式打開此文件." << endl;
}

 

沒有什麼新的難點不是嗎?

 

接下來我們就可以讀取文件內容了。

 

要讀取文件內容,有三種不同的方式:

  • 一行一行地讀取,用getline()函數

    一個詞一個詞地讀取,用>>

    一個字符一個字符地讀取,用get()函數

     

    我們分別來學習這三種方式:

     

    一行一行地讀取

     

    第一種方式可以一次讀取整一行的內容,將其存儲在一個字符串裡。舉例如下:

     

    string line; // 儲存整行內容的字符串變量
    getline(myStream, line); //讀取整一行,存儲到line中

     

    此函數的原理和cin是類似的。

     

    一個詞一個詞地讀取

     

    第二種方式,其實你也早就知道了,畢竟聰慧如你嘛。舉例如下:

     

    double number;
    myStream >> number; //從文件中讀取一個浮點數
    string word;
    myStream >> word; //從文件中讀取一個單詞

     

    這個方法會讀取當前所在的文件位置處的內容和之後的一個空格("詞"並不是我們平時說的一個單詞,而是以空格來分隔的,假如中間沒有空格,那麼就是一個詞,例如heusyg3這是一個詞,但是heu syg3卻被認為是兩個詞,因為中間存在空格)。讀取的內容根據變量的類型會被轉換成double,int,string,等等。

     

    一個字符一個字符地讀取

     

    第三種方式,我們之前沒學過,不過也很簡單就是了。舉例如下:

     

    char a;
    myStream.get(a);

     

    上面的代碼讀取一個字符,將其存儲在char型變量a中。

     

    這個方法可以讀取所有字符,不管是字母,空格,回車符,制表符,等等。

     

    還記得在【C++探索之旅】第一部分第五課:簡易計算器中,我們學習過cin的用法嗎?還記得我們說過在cin>>和getline之間需要使用cin.ignore()嗎?因此,這裡我們從一個詞一個詞地讀取(用cin>>)轉換到一行一行地讀取(用getline()),也需要在之間加入ignore()。不過,因為我們這裡是在讀取文件,所以不能用cin.ignore(),而要使用ifstream的ignore方法,如下所示:

     

    ifstream myStream("C:/Cpp/Files/scores.txt");
    
    string word;
    myStream >> word; //讀取一個詞
    
    myStream.ignore(); //改變讀取方式
    
    string line;
    getline(myStream, line); //讀取一整行

     

    一次讀取整個文件

     

    很多時候,我們會希望讀取整個文件。我們已經學習了如何讀取文件,但是還沒學習當到達文件結尾時,如何停止。

     

    為了獲知我們是否還可以繼續讀取,可以用getline函數的返回值。getline函數的返回值是一個bool(布爾值),如果等於true,還可以繼續讀,說明還沒到文件末尾;如果等於false,那麼說明已經讀取了文件的最後一行或者出錯了。在false的情況下,就不能再繼續讀取了。

     

    還記得我們學過的循環嗎?只要還沒到達文件末尾(getline函數返回是true),我們就繼續讀取文件。while循環就是最好的選擇啦。看如下例子:

     

    #include 
    #include 
    #include 
    using namespace std; 
    
    int main()
    {
     ifstream file("C:/Cpp/Files/scores.txt"); // 嘗試打開文件
     
     if(file)
     {
     //文件順利打開,可以讀取了
     
     string line; //存儲讀取的一整行的變量
     
     while(getline(file, line)) //只要沒到達文件末尾,我們就一直一行一行地讀取
     {
     cout << line << endl;
     //在控制台顯示讀取的行
     //或者隨便你拿這一行干什麼,由你決定
     }
     }
     else
     {
     cout << "出錯: 無法以讀的形式打開此文件." << endl;
     }
     
     return 0;
    }

     

    一旦我們讀取了這些行,我們就可以非常方便地操作它們了。在上面的例子中,我們只是把讀取的每一行顯示在控制台中,但是你可以隨便怎麼用。

     


    一些小技巧

     

    這一課的最後,我們來學習幾個小技巧,這樣文件讀寫我們就學習得差不多了。

     

    提前關閉文件

     

    我們已經知道怎麼打開一個文件,但還沒演示如何關閉文件。倒不是因為我忘記了,而是之前關閉文件顯得沒有那麼必要。一旦我們跳出了文件流聲明的區塊,打開的文件就會被自動關閉。例如:

     

    void f()
    {
     ofstream myStream("C:/Cpp/Files/scores.txt"); //打開文件
     
     // 操作文件
     
    } //當我們跳出這個函數,文件就自動被關閉了

     

    因此,並不需要做任何操作來顯式地關閉文件。

     

    但是,有時候我們想要提前關閉文件,在它被自動關閉前。為了達到這個目的,我們必須"不擇手段"... 哦,不是,是使用close函數。例如:

     

    void f()
    { 
        ofstream myStream("C:/Cpp/Files/scores.txt"); 
        //打開文件C:/Cpp/Files/scores.txt
        
        //使用文件 
        
        myStream.close(); //關閉文件
        //自此,我們將不能再往文件裡寫東西了
    }

     

    同樣地,我們也可以推遲打開文件。用open函數。例如:

     

    void f(){ 
        ofstream myStream; //聲明文件流,但沒有綁定文件
     myStream.open("C:/Cpp/Files/scores.txt"); 
     //打開文件C:/Cpp/Files/scores.txt
    
     //使用文件 
    
     myStream.close(); //關閉文件 
     //自此,我們將不能再往文件裡寫東西了
    }

     

    正如你所見,以上的操作都很簡單。然而,在大部分時候,沒必要使用open和close函數來顯示地打開和關閉文件。

     

    文件裡的游標

     

    我們再來深入一些技術細節,"研究"一下文件的讀取是怎麼運作的。

     

    你還記得平時用文本編輯器的時候,我們在編輯文本時總會有一個一閃一閃的光標(cursor),指示了我們當前編輯的位置嗎?如下圖所示:

     

    \

    可以看到,目前光標位於Oscar的後面。

     

    在C++中操作文件時,也是同樣的原理。有一個游標(cursor)一直指示當前在文件中的位置。

     

    例如,當我們運行這一行的時候:

     

    ifstream file("C:/Cpp/Files/scores.txt");

     

    文件C:/Cpp/Files/scores.txt會被打開,游標會定位於文件最開始處。

     

    如果之後我們讀取第一個詞,就會讀取到Oscar這個詞。讀取完之後,我們的游標就會位於下一個單詞的開始處了,如下圖所示:

     

    \

    可以看到,現在游標位於is這第二個詞的開始處了。然後我們可以接著讀取第二個詞,第三個,... 一直到文件結束。

     

    但如果這樣的話,我們只能按順序讀取文件,這可太束縛了。我們需要自由,需要飛翔,"在你的心上,自由地飛翔~" (小編,你的藥已經准備好了...)

     

    幸好,我們能夠在文件中移動,說到移動,那就是移動那個cursor(游標)了。例如,我們可以說"我要移動到距離文件開始處20個字符的地方",或者"我要從當前位置前進32個字符"。這樣,我們就可以很方便地讀取我們真正想要的內容了。

     

    首先,我們要了解游標目前位於哪裡。然後才能正確地移動。

     

    獲得在文件中的位置

     

    有一個方法可以獲知當前我們的游標位於文件的第幾個字符處(從文件開始處算起)。不過,對於輸入文件流(ifstream)和輸出文件流(ofstream),所用的函數不一樣,而且名字也有點古怪,我們列在下面:

     

    針對ifstream

    針對ofstream

    tellg()

    tellp()

     

    然而,這兩個函數的使用方法完全一樣。因此只介紹其中一個就可以了。舉例如下:

     

    ofstream file("C:/Cpp/Files/scores.txt");
    int position = file.tellp(); //獲取當前位置
    cout << "目前位於文件中的第" << position << "個字符處." << endl;

     

    在文件中移動

     

    用於在文件中移動的函數也有兩個,成對的,每一個對應一種流的形式:

     

    針對ifstream

    針對ofstream

    seekg()

    seekp()

     

    用法和之前的兩個函數類似。

     

    這兩個函數接受兩個參數:一個是在文件中的位置,另一個是相對文件中的位置的距離數(字符數/字節數)。

     

    myStream.seekp(numberOfCharacters, position);

     

    對於此函數的position參數,有三種可能的位置:

     

    1. 文件開始處 : ios::beg ;

      文件末尾處 : ios::end ;

      當前位置 : ios::cur.

       

      例如,我想要移動到距離文件開始處10個字符的地方,我會這麼做:

       

      myStream.seekp(10, ios::beg);

       

      假如我想要移動到距離當前游標所在位置的20個字符處,我會這麼做:

       

      myStream.seekp(20, ios::cur);

       

      相信你已經理解啦。

       

      獲知文件大小(所包含字節數)

       

      這第三個小技巧需要用到前兩個。為了獲知文件的大小,我們首先移動到文件末尾,然後詢問我們所在的位置。你知道怎麼做了嗎?一起來看看吧:

       

      #include 
      #include 
      using namespace std;
      
      int main()
      {
       ifstream file("C:/Cpp/Files/scores.txt"); //打開文件
       
       file.seekg(0, ios::end); //移動到文件末尾
       
       int size;
       size = file.tellg();
       //在文件結尾處調用tellg這個函數,以獲得目前位於第幾個字符處,因此也就知道了文件的大小
       
       cout << "文件的大小是: " << size << "個字節." << endl;
       
       return 0;
      }

       

      好了,我們學完了文件讀寫的大致概念。不過肯定不只於此,還有很多知識點需要慢慢在實踐中去探索。

       


      總結

       

      • 在C++中,為了能讀寫文件,需要引入fstream頭文件。

        為了寫入文件,我們需要創建一個ofstream對象;為了讀取文件,我們需要創建一個ifstream對象。

        寫入文件的操作其實很類似 cout : myStream << "文本"; 讀取文件的操作其實很類似 cout : myStream >> variable;

        可以用getline()函數一行一行地讀取文件。

        游標(cursor)指示了寫入操作或讀取操作時,在文件中的位置。如果需要,可以移動這個游標。

         


        第一部分第十一課預告

         

        今天的課就到這裡,一起加油吧!

        下一課我們學習:小練習,猜單詞


         
    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved