1. 程式人生 > >C++標準I/O庫

C++標準I/O庫

流介紹

標準I/O類的標頭檔案

 

 <iostream>  包含istream、ostream、iostream這三個類。其中,iostream由istream和ostream派生而來。
 <fstream>  包含ifstream、ofstream、fstream這三個類。其中,ifstream由istream派生,ofstream由ostream派生,fstream由iostream派生。
 <sstream>  包含istringstream、ostringstream、stringstream這三個類。其中,istringstream由istream派生,ostringstream由ostream派生,stringstream由iostream派生。

 

 注意:標準庫I/O物件不允許拷貝或賦值。

   C++標準庫的流類完整關係圖如下:(注意:我發現一個問題,下面的圖在開啟網頁時有時顯示模糊,顯示的圖比實際的小,有兩種方法:一是多重新整理幾次就能刷出來;二是另外顯示該圖片^_^)

   其中,typedef basic_ios<char, char_traits<char> > ios;
        typedef basic_istream<char, char_traits<char> > istream;
        typedef basic_ostream<char, char_traits<char> > ostream;
        typedef basic_iostream<char, char_traits<char> > iostream;
        typedef basic_istringstream<char> istringstream;
        typedef basic_ostringstream<char> ostringstream;
        typedef basic_stringstream<char> stringstream;

流狀態

 

   ios_base::iostate  機器相關的整型型別名,用於定義流狀態。I/O標準庫的基類ios中定義了四個為靜態成員的該型別的標誌位:

 

 ios_base::badbit  該標誌位表示該I/O流發生了嚴重錯誤(常指物理破壞,沒法修正)
 ios_base::eofbit  該標誌位表示該I/O流已經到達檔案尾
 ios_base::failbit  該標誌位表示該I/O流出現了失敗的I/O操作
 ios_base::goodbit  該標誌位表示該I/O流狀態正常

 
注意:各標誌位可以用|運算子連線起來;為了方便,可以用ios代替ios_base來使用這些標誌位(標誌位是ios_base的靜態常量成員),後面內容中出現的ios_base也可以這樣。

 

檢測流狀態:

 

 ios::bad()  如果流的badbit標誌位被設定,則返回true;否則返回false
 ios::eof()  如果流的eofbit標誌位被設定,則返回true;否則返回false。如果eofbit被設定,那麼failbit也將被設定。
 ios::fail()  如果流的failbit標誌位被設定,則返回true;否則返回false
 ios::good()  如果流的goodbit標誌位被設定,則返回true;否則返回false。僅當bad(),eof(),fail()都返回 false時,good()才返回true。

   除了可以直接呼叫流的這些成員函式進行流狀態檢測外,很多時候我們還可以把流物件直接用於布林表示式中進行測試。例如:
   int i;
   while (cin >> i)
      cout << i << endl;
在這種情況下,流到void*型別的轉換operator void*()被隱式呼叫,然後再隱式轉換為bool型。

   為什麼流類不直接定義一個到bool型別的隱式轉換呢?這是因為這樣做可能會引起錯誤。因為bool型可以隱式轉換到int型,從而使流類物件可以隱式轉換到int型,這樣極容易引用錯誤。例如,原本打算輸入“cin >> i”,但實際上卻輸入了“cin > i”,漏掉了一個“>”,如果有流物件到bool的隱式轉換,那麼“cin > i”語法上就是正確的,相當於最終兩個int型的比較。顯然,我們不希望這種情況下發生。另外,由於歷史原因,bool型在最先的C++中是不存在的,所以轉換到了void*型別。

設定/獲取流狀態:
    

 ios_base::clear(state)   將流s標誌位設定為state,預設引數為goodbit
 ios_base::setstate(state)  為流s新增指定的標誌位state,原來的標誌位仍然存在,相當於呼叫clear(state | rdstate())
 ios_base::rdstate()  返回流s的當前標誌位

輸出緩衝區的管理

 

   下面幾種情況將導致輸出緩衝區中的內容被重新整理到輸出裝置或檔案:
   1) 程式正常結束。作為main返回工作的一部分,將清空所有輸出緩衝區;
   2) 在一些不確定的時候,緩衝區可能已經滿了。在這種情況下,緩衝區將會在寫下一個值之前重新整理;
   3) 用操作符(manipulator)顯式地重新整理緩衝區,例如 endl,flush等;
   4) 在每次輸出操作執行後,用unitbuf操縱符設定流的內部狀態,從而清空緩衝區;
   5) 可將輸出流與輸入流關聯(tie) 起來。在這種情況下,在讀輸入流時將重新整理其關聯的輸出流緩衝區。

 

下面列舉用於重新整理緩衝區的操縱符:

 

 endl操縱符  輸出一個換行符,並重新整理緩衝區
 ends操縱符  輸出一個空字元null,然後重新整理緩衝區
 flush操縱符  直接重新整理緩衝區,不新增任何字元
 unitbuf操縱符  它在每次執行完寫操作後都重新整理緩衝區

   例如:
      cout<<unitbuf<<"first"<<" second"<<nounitbuf;
   等價於:
      cout<<"first"<<flush<<" second"<<flush;
   其中,nounitbuf操縱符將流恢復為使用正常的、由系統管理的緩衝區重新整理方式。

 

   將輸入和輸出關聯起來,用tie函式實現:
      basic_ostream<E, T> *tie() const;
      basic_ostream<E, T> *tie(basic_ostream<E, T> *str);


   tie函式可以由istream或ostream物件呼叫,使用一個指向ostream物件的指標形參。第一個函式返回上次儲存的被關聯的ostream物件指標,第二個函式設定新的關聯物件,並返回上次關聯的物件的指標。如果第二個函式的引數為0,則斷開兩個物件之間的關聯。例如:
   cin.tie(&cout);      // tie cin and cout
   ostream* old_tie = cin.tie();
   cin.tie(0);          // break tie between cin and cout
   cin.tie(&err);       // a new tie
   cin.tie(old_tie);    // restablish tie between cin and cout

 

檔案流

 

   如果檔案流已經與一個指定的檔案相關聯,若要把該檔案流與另一個檔案關聯,則必須先關閉(close)現在的檔案,再開啟(open)另一個檔案。open函式會檢查是否已經有檔案被開啟;如果有,則設定failbit標誌位,以指示錯誤,並且此時對檔案流的任何讀寫操作都會失敗。此時,可以清除該標誌,然後可以繼續對前面的檔案進行操作。

檔案模式

 

 ios_base::in  讀模式
 ios_base::out  寫模式
 ios_base::app  追加模式
 ios_base::ate  開啟檔案後立即定位到檔案尾
 ios_base::trunc  開啟檔案時清空檔案內容(如果有的話)
 ios_base::binary  以二進位制模式開啟檔案


   上面的標誌位被宣告為類ios_base的靜態常量成員。其中,out、truc、app模式只能用於與ofstreamt和fstream物件關聯的檔案;in模式只能用於與ifstreamt和fstream物件關聯的檔案;ate和binary模式可以用於所有檔案。

   如果以binary模式開啟檔案,則檔案流將以位元組序列處理檔案內容,不會對內容作任何解釋;否則,預設用文字模式開啟檔案,不同的系統可能會對檔案內容作一些解釋轉換。比如,在非UNIX系統如Windows系統中,換行符/n會被解釋成回車換行/r/n到檔案系統,/r/n會被解釋成/n到記憶體。而在UNIX系統中,二進位制模式和文字模式是沒有區別的。

   預設情況下,與ifstream流物件關聯的檔案將以in模式開啟;與ofstream關聯的檔案則以out模式開啟,並且以out模式開啟的檔案的內容會被清空(相當於同時也指定了trunc模式);與fstream關聯的檔案則以in和out模式開啟。

 

   如果開啟與fstream關聯的檔案時,只用了out模式,而不指定in模式,則檔案內容會被清空;如果指定了trunc模式,不論是否指定in模式,檔案內容都會被清空。

 

檔案模式的有效組合

 

 out  開啟檔案進行寫操作,刪除檔案中已有資料;如果檔案不存在,則建立檔案。
 app  開啟檔案進行寫操作,在檔案尾追加資料;若檔案是與ofstream流關聯,那麼,當檔案不存在時建立檔案;若是與fstream流關聯,那麼,當檔案不存在時操作失敗。
 out | app  追加資料;如果檔案不存在,則建立檔案。
 out | trunc  與out模式相同
 in  開啟檔案進行讀操作
 in | out  開啟檔案進行讀、寫操作,並定位於檔案形頭處
 in | out | trunc  開啟檔案進行讀、寫操作,並且刪除檔案中已有資料

字串流

 

   字串流(sstream)定義了一個以string物件為形參的建構函式。對sstream物件的讀寫操作實際上是對該物件中的string物件進行操作。

 

   sstream類還定義了一個名為str()的成員,用來讀取或設定sstream物件所操縱的string物件。
      basic_string<E, T, A> str() const;     
      void str(basic_string<E, T, A>& x);

 

字串流的通常用法


   用來實現格式化資料輸入輸出,相當於C語言中的fscanf和fprintf函式的功能。例如:

 

int var1 = 5, var2 = 10;
stringstream ss;
ss<<"Var1: "<<var1<<" Var2: "<<var2;
cout<<"SS<<: "<<ss.str()<<endl;
var1 = var2 = 0;
string dump;
ss>>dump>>var1>>dump>>var2;
cout<<"SS>>: Var1: "<<var1<<", Var2: "<<var2<<endl;

 


流的格式化I/O

操縱符

 

<iostream>中定義的操縱符(帶*號的表示是預設使用的)

 

 boolalpha  將真和假顯示為字串
*noboolalpha  將真和假顯示為1,0
 showbase  產生數的基數字首
*noshowbase  不產生數的基數字首
 showpoint  總是顯示小數噗
*noshowpoint   有小數部分才顯示小數點
 showpos  顯示非負數中的+(0也顯示+)
*noshowpos   不顯示非負數中的+
 uppercase  在十六進位制中列印0X,科學記數法中列印E
*nouppercase   在十六進位制中列印0x,科學記數法中列印e
*dec  用十進位制顯示
 hex  用十六進位制顯示
 oct  用八進位制顯示
 left  在對齊,在值的右邊增加填充字元
*right  右對齊,在值的左邊增加填充字元
 internal  兩邊對齊,在值和符號之間填充字元
 fixed  用小數形式顯示浮點數
 scientific  用科學記數法顯示浮點數
 flush  重新整理ostream緩衝區
 ends  插入空字元,然後重新整理ostream緩衝區
 endl  插入換行符,然後重新整理ostream緩衝區
 unitbuf  在每個輸出操作之後重新整理緩衝區
*nounitbuf  恢復常規緩衝區重新整理
*skipws  為輸入操作符>>跳過空白字元(空格、製表位、換行)
 noskipws  不為輸入操作符跳過空白
 ws  “吃掉”空白

   預設情況下,用於顯示浮點數的記數法取決於數的大小:如果數很大或者很少,將按科學記數法顯示;否則,使用固定位數的小數顯示。

 

   如果設定了scientific或者fixed,由於不存在相應的操縱符來恢復預設值,只能通過呼叫unsetf成員來取消scientific或fixed所做的改變:unsetf(ios_base::floatfield);(函式原型:void unsetf(fmtflags mask);)

 

<iomanip>中定義的操縱符

 

 setfill(char ch)  設定ch為空白填充字元
 setprecision(int n)  將浮點精度設定為n
 setw(int w)  讀寫w個字元的值
 setbase(int b)  按基數b輸出整數,n取值為8、10、16;若為其它值,則基數為10
 setiosflags(ios_base::fmtflags n)  相當於呼叫setf(n)
 resetiosflags(ios_base::fmtflags n)  清除由n代表的格式化標誌

   預設情況下,精度指定數字的總位數(小數點之前和之後),預設為6位。使用fixed或者scientific之後,精度指小數點之後的位數。

 

   setw不改變流狀態,只對後面的一個數據輸出有效。其它操縱符改變流格式狀態後,I/O流保留改變後的格式狀態。

 

flag成員函式

 

   ios類提供了flag成員函式用來設定和恢復流的格式狀態(所有標誌位),函式原型如下:

      ios_base::fmtflags flags() const;                    // 返回流的當前格式狀態
      ios_base::fmtflags flags(ios_base::fmtflags fmtfl);  // 設定流的格式狀態

 

setf和unsetf成員函式

 

   ios類提供了這兩個成員函式用來設定或者取消某個標誌位,其函式原型如下:

      void setf(ios_base::fmtflags mask);
      ios_base::fmtflags setf(ios_base::fmtflags fmtfl, fmtflags mask);
      void unsetf(ios_base::fmtflags mask);    // 清除指定標誌位

 

其中,第一個setf版本適用於開關標誌位,這些開關標誌位有:

   ios_base::boolalpha, ios_base::skipws, ios_base::showbase, ios_base::showpoint, ios_base::uppercase, ios_base::showpos,  ios_base::unitbuf

   第二個setf版本適用於格式化域標誌位,一次只能設定這些標誌中的一個。這些格式化域及其標誌位如下:

   ios_base::basefield:   ios_base::dec, ios_base::hex, ios_base::oct

   ios_base::floatfield:  ios_base::scientific, ios_base::fixed

   ios_base::adjustfield: ios_base::left, ios_base::right, ios_base::internal

 

   例如,

      s.setf(ios_base::boolalpha);                   // 設定布林值顯示為字元形式

      s.seft(ios_base::scientific, ios_base::floatfield); // 設定小數輸出形式為科學記數法

      s.unsetf(ios_base::floatfield);                // 清除floatfield域的標誌位

 

其它成員函式

  

   ios類還提供了其它幾個成員函式來控制輸出域格式,其函式原型如下:

      int ios_base::width();              // 返回當前寬度,預設為0

      int ios_base::width(int n);         // 設定寬度

      char ios::fill();

      char ios::fill(char n);

      int ios_base::precision();

      int ios_base::precision(int n);

流的未格式化的輸入輸出操作

單位元組操作

 

 is.get(ch)  將istream is的下一個位元組放入字元ch中,返回is
 os.put(ch)  將位元組ch放入ostream os中,返回os
 is.get()  返回is的下一位元組作為一個int值
 is.putback(ch)  將字元ch放回is,返回is
 is.unget()  將is退回一個位元組,返回is
 is.peek()  將下一位元組作為int值返回但不移出它(不移動流緩衝區指標)

 

   一般而言,保證能夠在下一次讀之前放回最多一個值,也就是說,不保證能夠連續呼叫putback或unget而恢復原來的流狀態。

 

   為什麼get()和peek()要返回int型,而不是char型呢?原因是為了允許返回一個檔案結束符。由於允許給定字符集使用char範圍的每一個值來表示實際字元,因此,該範圍中沒有額外值用來表示檔案結束符。相反,這些函式把字元轉換為unsigned char,然後將那個值提升為int,因此,即使字符集有對映到負值的字元,從這些操作返回的值也將是一個正值。通過將檔案結束符作為負值返回,標準庫將保證檔案結束符區別於任意合法字元值。檔案結束符EOF定義於<iostream>檔案中,為一個為負值的const變數,用它來標誌檔案是否結束。例如:

   int ch;   // int, not char!

   while ((ch = cin.get()) != EOF)

      cout.put(ch);

 

多位元組操作

 

 is.get(buf, size, delim),其函式原型為:
 basic_istream& get(E *s, streamsize n, E delim = '/n');
 從is中讀入size個位元組並將它們儲存到buf所指向的空間中,返回is。當讀操作遇到delim字元、或者檔案結束符、或者已經讀入了size個位元組,那麼讀操作結束。如果遇到delim,它將被留在輸入流中。

 is.getline(buf, size, delim),其函式原型為:
basic_istream& getline(E *s, streamsize n, E delim = '/n');

 與上面的get行為相似,區別是讀取並丟棄delim。
 is.read(buf, size)  讀取size個位元組到buf中,返回is
 is.gcount()  返回最後一個未格式化讀操作從流is中讀到的位元組數
 os.write(buf, size)  將size個位元組從陣列buf寫到os,返回os
 is.ignore(size, delim)  讀並忽略size個字元,直到遇到delim,但不包括delim。size的預設引數為1,delim預設引數為檔案結束符。

 

   由於將字元放回流中的單字元操作也是未格式化輸入操作,如果在呼叫gcount之前呼叫了peek、unget或者putback,則返回值是0。
 
   舉例:當輸入緩衝區發生錯誤時,需要清空緩衝區時,可以這樣做:
        if (!cin) { 
            cin.clear(); 
            cin.ignore(numeric_limits<int>::max(), '/n');
        }
   另外,需要注意的是使用低階I/O操作容易出錯,提倡使用標準庫的高階抽象。例如,返回int值的I/O操作就是一個很好的例子。

 

   將get或其它返回int值的函式的返回值賦給char物件而不是int物件,是一個常見的錯誤。至於這種錯誤,具體在機器上發生什麼行為,取決於機器和輸入資料。例如,在將char實現為unsigned char的機器上,這是一個死迴圈:

   char ch;

   while ((ch = cin.get()) != EOF)

      cou.put(ch);

這是因為,當get返回EOF的時候,那個值將被轉換為unsigned char值,轉換後的值不再等於EOF的整型值,形成死迴圈。

流的隨機訪問

   I/O流提供了兩個成員函式來實現隨機訪問:定位函式(seek)和查詢函式(tell)。如下表所示:
 
 seekg  重新定位輸入流中的讀指標
 tellg  返回輸入流中讀指標的當前位置
 seekp  重新定位輸出流中的寫指標
 tellp  返回輸出流中寫指標的當前位置

注意:在每個流中(即使是可同時輸入輸出的流,如fstream),只有一個讀(寫)指標,標準庫將g位置和p位置都對映到這個指標。所以,在讀和寫之間切換時,必須進行seek來重新定位標記。

 

   另外,由於istream和ostream型別一般不支援隨機訪問,所以,流的隨機訪問只適用於fstream和sstream型別。

 

   seekg和seekp均有兩個過載版本,一個使用絕對地址,另一個使用相對偏移。其函式原型如下:

      basic_istream& seekg(pos_type pos);
      basic_istream& seekg(off_type off, ios_base::seek_dir way);

      basic_ostream& seekp(pos_type pos);
      basic_ostream& seekp(off_type off, ios_base::seek_dir way);

 

   在上面的函式中,way引數有幾個預定義取值:

      ios_base::beg    表示流的開頭

      ios_base::cur    表示流的當前位置

      ios_base::end    表示流的末尾

   另外,seek_dir、pos_type和off_type均是ios_base類裡面的型別。

  

   下面是兩個tell的函式原型:

      pos_type tellg()
      pos_type tellp();

 


流類和異常

   除了手工檢查流狀態外,還可以利用異常機制來解決流類錯誤問題。流的成員函式exceptions()接受一個引數,這個引數用於表示程式設計師希望在哪個流狀態標誌位出現時丟擲異常 。當流遇到這樣的狀態時,就丟擲一個std::ios_base::failure型別的異常,其繼承自std::exception。函式原型如下:

   iostate exceptions() const;
   iostate exceptions(iostate except);