C++輸入/輸出流類庫

轉載地址:http://www.weixueyuan.net/cpp/rumen_9/

在C++中,數據從一個對象到另一個對象的傳送被抽象爲“流”,由它負責在數據的產生者和使用者之間建立聯繫,並管理數據的流動。

在現代操作系統中,一切輸入/輸出設備,包括鍵盤、顯示器、打印機、網卡、磁盤、聲卡等,都被視爲廣義的文件。在C++中,與這些文件的交互,即數據的輸入/輸出,是通過包含在C++標準庫裏的輸入/輸出(I/O)流類庫來實現的。

本章重點介紹怎樣把數據保存到磁盤文件中。
本章內容:
C++的基本流類體系
C++輸入輸出的格式控制
C++標準設備的輸入/輸出(cin,cout,cerr,clog,>>
C++文件的打開與關閉
C++文件的讀/寫(文本文件和二進制文件)
C++文件的隨機訪問
C++字符串流
C++文件與對象

C++的基本流類體系

流類體系

整個流類體系是一個派生類體系,如下圖所示。


圖 輸入/輸出流類派生體系

按ANSI C++標準,類ios是抽象類,它的析構函數是虛函數,它的構造函數爲保護的,作爲所有基本流類的基類。VC++中有一個構造函數ios (streambuf*)爲公有,與ANSI C++不同。

在流類庫中,最重要的兩部分功能爲標準輸入/輸出(standard input/output)和文件處理。

在C++的流類庫中定義了四個全局流對象:cin,cout,cerr和clog。可以完成人機交互的功能。
  • cin:標準輸入流對象,鍵盤爲其對應的標準設備。帶緩衝區的,緩衝區由streambuf類對象來管理。
  • cout:標準輸出流對象,顯示器爲標準設備。帶緩衝區的,緩衝區由streambuf類對象來管理。
  • cerr和clog:標準錯誤輸出流,輸出設備是顯示器。爲非緩衝區流,一旦錯誤發生立即顯示。

要使用這四個功能,必須包含<iostream.h>文件。

提取運算符“>>”(stream_extraction operator)和插入運算符“<<”(stream_insertion operator),執行輸入/輸出操作。
  • “提取”的含義是指輸入操作,可看作從流中提取一個字符序列。
  • “插入”的含義是指輸出操作,可看作向流中插入一個字符序列。

文件處理完成永久保存的功能。在VC++的MFC編程中採用了序列化(Serialization)。


C++輸入輸出的格式控制

C++在類ios中提供格式化輸入輸出。

格式控制符

使用格式控制符,可以進行格式化輸入輸出。這些格式對所有文本方式的輸入輸出流均適用。

格式控制符定義爲公有的無名的枚舉類型:
enum{
       skipws=0x0001, //跳過輸入中的空白字符
       left=0x0002, //輸出左對齊
       right=0x0004, //輸出右對齊
       internal=0x0008, //在輸出符號或數制字符後填充
       dec=0x0010, //在輸入輸出時將數據按十進制處理
       oct=0x0020, //在輸入輸出時將數據按八進制處理
       hex=0x0040, //在輸入輸出時將數據按十六進制處理
       showbase=0x0080, //在輸出時帶有表示數制基的字符
       showpoint=0x0100, //輸出浮點數時,必定帶小數點
       uppercase=0x0200, //輸出十六進制,用大寫
       showpos=0x0400, //輸出正數時,加”+”號
       scientific=0x0800, //科學數方式輸出浮點數
       fixed=0x1000, //定點數方式輸出實數
       unitbuf=0x2000, //插入後,立即刷新流
       stdio=0x4000 //插入後,立即刷新stdout和stderr
}

該枚舉量說明中的每一個枚舉量,實際對應兩字節數據(16位)中的一個位,所以可以同時採用幾個格式控制,只要把對應位置1即可,這樣既方便又節約內存。

取多種控制時,用或“|”運算符來合成,合成爲一個長整型數,在ios中爲:
protected:
      long x_flags;

配合使用的格式控制標誌

protected:
       int x_precision; //標誌浮點數精度,默認爲6位
       int x_width; //輸出域寬,默認域寬爲0,
              //重設域寬只對其後第一輸出項有效,如域寬不足,則不受限制
       char x_fill; //標誌域寬有富餘時填入的字符


【例9.1】整型數輸出。(查看源碼

【例9.2】浮點數輸出。(查看源碼

流操作子(setiosflags stream manipulator)

流格式控制成員函數的使用比較麻煩,可改用流操作子(Setiosflags Stream Manipulator)。例如setw()等,可代替流類中的格式控制成員函數。

注意,絕大多數流操作子僅適用於新的C++標準流類庫(頭文件不帶.h),常用流操作子如下表所示。

cin,cout和clog都是緩衝流。對輸出而言,僅當輸出緩衝區滿纔將緩衝區中的信息輸出,對輸入而言,僅當輸入一行結束,纔開始從緩衝區中取數據,當希望把緩衝區中的信息立即輸出,可用flush,加endl也有同樣功能,回車並立即顯示,不必等緩衝區滿(endl清空緩衝區)。

【例9.2_1】採用流操作子的浮點數輸出。(查看源碼


C++標準設備的輸入/輸出(cin,cout,cerr,clog,>>,<<)

本節對cin,cout,cerr,clog,>>和<<(提取和插入運算符)的使用細節作進一步討論。

提高標準輸入/輸出的健壯性

◆ 1、標準設備輸入使用要點
  • cin爲緩衝流。鍵盤輸入的數據保存在緩衝區中,當要提取時,是從緩衝區中拿。如果一次輸入過多,會留在那兒慢慢用,如果輸入錯了,必須在回車之前修改,如果回車鍵按下就無法挽回了。只有把輸入緩衝區中的數據取完後,纔要求輸入新的數據。不可能用刷新來清除緩衝區,所以不能輸錯,也不能多輸!
  • 輸入的數據類型必須與要提取的數據類型一致,否則出錯。出錯只是在流的狀態字state(枚舉類型io_state)中對應位置位(置1),程序繼續。所以要提高健壯性,就必須在編程中加入對狀態字state的判斷。
  • 空格和回車都可以作爲數據之間的分格符,所以多個數據可以在一行輸入,也可以分行輸入。但如果是字符型和字符串,則空格(ASCII碼爲32)無法用cin輸入,字符串中也不能有空格。回車符也無法讀入。
  • 輸入數以後再輸入字符或字符串:如果數後直接加回車,應該用cin.get()提取回車。如果還有空格,則要清空緩衝區。
◆ 2、程序運行狀態
狀態字state爲整型,其的各位在ios中說明:
enum ios_state
{
       goodbit=0x00, //流正常
       eofbit=0x01,    //輸入流結束忽略後繼提取操作;或文件結束已無數據可取
       failbit=0x02, //最近的I/O操作失敗,流可恢復
       badbit=0x04, //最近的I/O操作非法,流可恢復
       hardfail=0x08     // I/O出現致命錯誤,流不可恢復,VC6.0++不支持
}

讀取狀態的有關操作如下:
inline int ios::rdstate() const //讀取狀態字
{return state;}

inline int ios:operator!() const //可用操作符!()代替fail()
{return state&(badbit|failbit);}

inline int ios::bad() //返回非法操作位
{ return state & badbit;}

inline void ios::clear(int _i) //人工設置狀態,可用來清狀態
{ lock();state=_i;unlock();}

inline int ios::eof() const //返回流(文件)結束位
{return state&eofbit;}

inline int ios::fail() const //返回操作非法和操作失敗這兩位
{return state&(badbit|failbit);}

inline int ios::good() const //正常返回1,否則返回0
{return state==0;}

◆ 3、舉例:【例9.3】提高輸入的健壯性。輸入時需要故意輸錯,以測試健壯性。 (查看源碼

標準輸入/輸出成員函數

◆ 1、輸入流成員函數聲明
(1)字符輸入:
    int istream::get();
    //提取一個字符,包括空格,製表,backspace和回車等,
    //與cin有所不同.注意返回爲整型
    istream&istream::get(char &);
    istream&istream::get(unsigned char &);
提取一個字符,放在字符型變量中

(2)字符串輸入:
    istream&istream::get(char *,int,char=’\n’);
    istream&istream::get(unsigned char *,int,char=’\n’);
    istream&istream::getline(char *,int,char=’\n’);
    istream&istream::getline(unsigned char *,int,char=’\n’);
提取的串放在第一個參數爲開始地址的存儲區(不查邊界);第二個參數爲至多提取的字符個數(指定爲n,最多取n-1個,再加一個字符串結束符);第三個參數爲結束字符,遇此字符則結束,默認爲回車換行符。

get系列函數要求單獨提取結束字符。getline提取字符串時如遇到指定結束符則提取該結束符,但不保存在串中。這兩個函數都會在提取的一系列字符後加一個串結束符,返回值爲對象本身(*this)。

(3)其他函數:
函數gcount()返回最後一次提取的字符數量,包括回車:
    int istream::gcount();

函數ignore()讀空(指定一個大的數量)緩衝區:
    istream&istream::ignore(int=1,int=EOF);
第一個參數爲要提取的字符數量,默認爲1;第二個參數爲結束字符,提取該結束字符,但對所提取的字符不保存不處理,作用是空讀。第二個參數的默認值EOF爲文件結束標誌。

在iostream中EOF定義爲-1,在int get()函數中,讀入輸入流結束標誌Ctrl+Z(^Z)時,函數返回EOF,爲了能表示EOF的“-1”值,返回類型爲int。採用cin.eof()函數,當前所讀爲EOF則返回非零,注意函數自身未從流中讀取。

【例9.4】 ignore()和gcount()函數使用。(查看源碼

◆ 2、輸出流成員函數聲明
    ostream&ostream::put(char);
    //輸出參數字符
    ostream&ostream::put(unsigned char);
    ostream&ostream::put(signed char);
    ostream&ostream::flush();
    //刷新一個輸出流,用於cout和clog

重載插入和提取運算符

重載必須保留原來的使用特性。重載只能在用戶定義類中,將重載的運算符的函數說明爲該類的友元函數:
    friend istream&operator>>(istream&,className&);
    friend ostream&operator<<(ostream&,className&);
函數的返回值是對輸入或輸出流的引用,這是爲了保證在cin和cout中可以連續使用“>>”或“<<”運算符,與所有“>>”和“<<”重載函數一致。第一個參數是輸入或輸出流的引用,作爲“>>”或“<<”的左操作數;第二個參數爲用戶定義類的引用,作爲右操作數,流用作函數參數,必須是引用調用,不能是傳值調用。

【例9.5】改進【例6.10】,重載插入運算符“<<”。(查看源碼

【例9.6】用戶定義的複數類Complex的輸入與輸出。(查看源碼



C++文件的打開與關閉

文件的基本概念

本節中文件指的是磁盤文件

C++根據文件(file)內容的數據格式,可分爲兩類:
  • 文本文件:由字符序列組成,在文本文件中存取的最小信息單位爲字符(character),也稱ASCII碼文件。
  • 二進制文件:存取的最小信息單位爲字節(Byte)。

C++把每個文件都看成一個有序的字節流,每一個文件或者以文件結束符(end of file marker)結束,或者在特定的字節號處結束,如下圖所示。



當打開一個文件時,該文件就和某個流關聯起來了。對文件進行讀寫實際上受到一個文件定位指針(file position pointer)的控制。

輸入流的指針也稱爲讀指針,每一次提取操作將從讀指針當前所指位置開始,每次提取操作自動將讀指針向文件尾移動。輸出流指針也稱寫指針,每一次插入操作將從寫指針當前位置開始,每次插入操作自動將寫指針向文件尾移動。

文件的打開與關閉

文件使用的5步驟:
①說明一個文件流對象,這又被稱爲內部文件:
    ifstream ifile;//只輸入用
   ofstream ofile;//只輸出用
    fstream iofile;//既輸入又輸出用


②使用文件流對象的成員函數打開一個磁盤文件。這樣在文件流對象和磁盤文件名之間建立聯繫。文件流中說明了三個打開文件的成員函數。
    void ifstream::open(const char*,int=ios::in,int=filebuf::openprot);
    voidofstream::open(const char*,int=ios::out,int=filebuf::openprot);
    void fstream::open(const char*,int,int=filebuf::openprot);

第一個參數爲要打開的磁盤文件名。第二個參數爲打開方式,有輸入(in),輸出(out)等,打開方式在ios基類中定義爲枚舉類型。第三個參數爲指定打開文件的保護方式,一般取默認。所以第二步可如下進行:
    iofile.open(“myfile.txt”,ios::in|ios::out);

上面三個文件流類都重載了一個帶默認參數的構造函數,功能與open函數一樣:
    ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot);
    ofstream::ofstream(const char*,int=ios::out,int=filebuf::openprot);
    fstream::fstream(const char*,int,int=filebuf::operprot);

所以①和②兩步可合成: fstream iofile(”myfile.txt”,ios::in|ios::out);

③打開文件也應該判斷是否成功,若成功,文件流對象值爲非零值,不成功爲0(NULL),文件流對象值物理上就是指它的地址。因此打開一個文件完整的程序爲:
fstream iofile(”myfile.txt”,ios::in|ios::out);
if(!iofile)
{ //“!”爲重載的運算符
       cout<<”不能打開文件:”<<”myfile,txt”<<endl;
       return -1;
} //失敗退回操作系統


④使用提取和插入運算符對文件進行讀寫操作,或使用成員函數進行讀寫,這在下一節中討論。

⑤關閉文件。三個文件流類各有一個關閉文件的成員函數 :
    void ifstream::close();
    void ofstream::close();
    void fstream::close();

使用很方便,如:
    iofile.close();

關閉文件時,系統把該文件相關聯的文件緩衝區中的數據寫到文件中,保證文件的完整,收回與該文件相關的內存空間,可供再分配,把磁盤文件名與文件流對象之間的關聯斷開,可防止誤操作修改了磁盤文件。如又要對文件操作必須重新打開。

關閉文件並沒有取消文件流對象,該文件流對象又可與其他磁盤文件建立聯繫。文件流對象在程序結束時,或它的生命期結束時,由析構函數撤消。它同時釋放內部分配的預留緩衝區。



C++文件的讀/寫(文本文件和二進制文件)

文本文件的讀寫

文本文件的順序讀寫:順序讀寫可用C++的提取運算符(>>)和插入運算符(<<)進行。

【例9.7】複製文件。(查看源碼

【例9.8】按行復制文本文件。(查看源碼

【例9.9】文本式數據文件的創建與讀取數據。(查看源碼

資源獲取是由構造函數實現,而資源釋放是由析構函數完成。所以與內存動態分配一樣,由文件重構對象放在構造函數中,把對象存入文件則放在析構函數中。參見後面章節。

二進制文件的讀寫

◆ 1、對二進制文件進行讀寫的成員函數
    istream&istream::read(char *,int);
    //從二進制流提取
    istream&istream::read(unsigned char*,int);
    istream&istream::read(signed char *,int);
    //第一個參數指定存放有效輸入的變量地址,第二個參數指定提取的字節數,
    //函數從輸入流提供指定數量的字節送到指定地址開始的單元

    ostream&ostream::write(const char *,int);
    //向二進制流插入
    ostream&ostream::write(const unsigned char *,int);
    ostream&ostream::write(const signed char *,int);
    //函數從該地址開始將指定數量的字節插入輸入輸出流

◆ 2、文件結束判斷:讀函數並不能知道文件是否結束,可用狀態函數int ios::eof()來判斷文件是否結束。必須指出系統是根據當前操作的實際情況設置狀態位,如需根據狀態位來判斷下一步的操作,必須在一次操作後立即去調取狀態位,以判斷本次操作是否有效。

◆ 3、舉例:【例9.10】創建二進制數據文件,以及數據文件的讀取。(查看源碼)這兩項操作設計爲成員函數,給出與【例9.9】不同的讀寫方式。

◆ 4、二進制文件優點:可以控制字節長度,讀寫數據時不會出現二義性,可靠性高。同時不知格式是無法讀取的,保密性好。文件結束後,系統不會再讀(見eofbit的說明),但程序不會自動停下來,所以要判斷文件中是否已沒有數據。如寫完數據後沒有關閉文件,直接開始讀,則必須把文件定位指針移到文件頭。如關閉文件後重新打開,文件定位指針就在文件頭。


C++文件的隨機訪問

在C++中可以由程序控制文件指針的移動,從而實現文件的隨機訪問,即可讀寫流中任意一段內容。

一般文本文件很難準確定位,所以隨機訪問多用於二進制文件。如【例9.9】中對象中兩個字符串是按實際串長存放的,不是按數組元素來存放的,而【例9.10】中是按數組長度來存放的,每個對象數據長度固定,所以便於隨機訪問。

隨機訪問指針控制字

在ios類中說明了一個公有枚舉類型:
enum seek_dir
{
       beg=0, //文件開頭
       cur=1, //文件指針的當前位置
       end=2 //文件結尾
};

設置“輸入流指針控制字”的成員函數

設置“輸入流指針控制字”的成員函數:
    istream&istream::seekg(streampos); //指針直接定位
    istream&istream::seekg(streamoff, ios::seek_dir); //指針相對定位
    long istream::tellg(); //返回當前指針位置

流的指針位置類型streampos和流的指針偏移類型streamoff定義爲長整型,也就是可訪問文件的最大長度爲4G。例:
    datafile.seekg(-20L,ios::cur);
    //表示將文件定位指針從當前位置向文件頭部方向移20個字節。

    datafile.seekg(20L,ios::beg);
    //表示將文件定位指針從文件頭向文件尾方向移20個字節。

    datafile.seekg(-20L,ios::end);
    //表示將文件定位指針從文件尾向文件頭方向移20個字節。

    tellg()和seekg()往往配合使用。
    //指針不可移到文件頭之前或文件尾之後。

設置“輸出流指針控制字”的成員函數

設置“輸出流指針控制字”的成員函數:
    ostream&ostream::seekp(streampos);
    ostream&ostream::seekp(streamoff,ios::seek_dir);
    long ostream::tellp();
爲了便於記憶,函數名中g是get的縮寫,而p是put的縮寫。對輸入輸出文件定位指針只有一個但函數有兩組,這兩組函數功能完全一樣。

舉例

【例9.11】使用隨機訪問對【例9.10】進行改造。(查看源碼


C++字符串流

字符流概念:字符串(string)也可以看作字符流。可以用輸入輸出操作來完成串流的操作。串流與內存相關,所以也稱內存流。

串流類包括ostrstreamistrstreamstrstream,它們在<strstrea.h>中說明。串流類對象可以保存字符,也可以保存整數、浮點數。串流類對象採用文本方式。

其構造函數常用下面幾個:
    istrstream::istrstream(const char * str);
    istrstream::istrstream(const char * str,int);
    ostrstream::ostrstream(char *,int,int=ios::out);
    strstream::strstream(char *,int,int);

其中第二個參數說明數組大小,第三參數爲文件打開方式。

【例9.12】字符串流類的應用。
#include<strstream>
#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int i;
    char str[36]="This is a book.";
    char ch;
    istrstream input(str,36);          //以串流爲信息源
    ostrstream output(str,36);
    cout<<"字符串長度:"<<strlen(str)<<endl;
    for(i=0;i<36;i++){
        input>>ch;             //從輸入設備(串)讀入一個字符,所有空白字符全跳過
        cout<<ch;                     //輸出字符
    }
    cout<<endl;
    int inum1=93,inum2;
    double fnum1=89.5,fnum2;
    output<<inum1<<' '<<fnum1<<'\0';  //加空格分隔數字
    cout<<"字符串長度:"<<strlen(str)<<endl;
    istrstream input1(str,0);      //第二參數爲0時,表示連接到以串結束符終結的串
    input1>>inum2>>fnum2;
    cout<<"整數:"<<inum2<<'\t'<<"浮點數:"<<fnum2<<endl; //輸出:整數:93 浮點數:89.5
    cout<<"字符串長度:"<<strlen(str)<<endl;
    return 0;
}


C++文件與對象

規範化操作:在面向對象的程序設計中,信息總是放在對象的數據成員裏。這些信息最終應該保存到文件中。當程序開始運行時要由打開的文件重新創建對象。在運行過程中,放在對象的數據成員裏的信息得到利用和修改。運行結束時必須把這些信息重新保存到文件中,然後關閉文件。

在面向對象的C++程序設計中,文件應該在構造函數中打開,並創建對象;而在析構函數中保存和關閉文件,並撤銷對象。當撤銷對象時,能自動釋放資源。釋放資源包括將對象中的信息再次存入磁盤文件。程序運行中,總要對保存在對象的數據成員裏的信息進行操作,這時應該將信息適時保存到相應的磁盤文件中,以免數據意外丟失。這些都是常規操作,是面向對象的C++程序設計的固定框架。

【例9.13】將商店的貨物,定義爲一個貨物數組類。數組對象動態建立,初始爲2個元素,不夠用時擴充一倍。用文本數據文件建立數組元素對象,要求放在構造函數中,而數據的保存和文件的關閉放在析構函數中。第一次運行時,建立空的數據文件,由鍵盤輸入建立數組元素對象,並寫入文件,程序退出時,關閉文件;下一次運行由該文件構造對象,恢復前一次做過的工作。(查看源碼

這是一個標準的面向對象的程序設計,也是對前面各章內容的小結。注意,本例使用了多重的插入運算符重載。 

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