IO庫設施
- istream? (輸入流)類型,提供輸入操作。
- ostream (輸出流)類型,提供輸出操作。
- cin,—個 istream對象,從標準輸入讀取數據。
- cout, 一個ostream對象,向標準輸出寫入數據。
- cerr, 一個。stream對象,通常用于輸出程序錯誤消息,寫入到標準錯誤。
- >>運 算 符 ,用來從一個istream對象讀取輸入數據。
- <<運算符,用來向一個stream對象寫入輸出數據。?
- getline函數(參見3.3.2節,第 78頁),從一個給定的istream讀取一行數據, 存入一個給定的string對象中
8.1 IO類
- iostream定義了用于讀寫流的基本類型,fstream定義了讀寫 命名文件的類型,sstream定義了讀寫內存string對象的類型。
- 為了支持使用寬字符的語言,標準庫定義了一組類型和對象來操縱wchar_t類型的 數 據 (參見2.1.1節,第 30頁)。寬字符版本的類型和函數的名字以一個w 開始。例如,wcin、wcout和 wcerr是分別對應cin、cout和 cerr的寬字符版對象。寬字符版本的類型和對象與其對應的普通char版本的類型定義在同一個頭文件中。例如,頭文件f stream 定義了 ifstream 和 wif stream 類型
IO類型間的關系
- 概念上,設備類型和字符大小都不會影響我們要執行的IO 操作。例如,我們可以用>>讀取數據,而不用管是從一個控制臺窗口,一個磁盤文件,還是一個string 讀取。類似的,我們也不用管讀取的字符能存入一個char對象內,還是需要一個wchar_t對象來存儲。
- 標準庫使我們能忽略這些不同類型的流之間的差異,這是通過繼承機制(inheritance) 實現的。利用模板(參見3.3節,第 87頁),我們可以使用具有繼承關系的類,而不必了解繼承機制如何工作的細節。我們將在第15章和18.3節 (第 710頁)介紹C++是如何支持繼承機制的。簡單地說,繼承機制使我們可以聲明一個特定的類繼承自另一個類。我們通常可以將一個派生類(繼承類)對象當作其基類(所繼承的類)對象來使用。
- 類 型ifstream和istringstream都繼承自istream。因此,我們可以像使用istream對象一樣來使用ifstream 和istringstream對象。也就是說,我們是如何使 用cin的,就可以同樣地使用這些類型的對象。例如,可以對一個ifstream 或istringstream對 象 調 用getline, 也 可 以 使 用>>從 一 個ifstream 或istringstream對象中讀取數據。類似的,類型ostringstream和ofstream都繼
承自ostream 。因此,我們是如何使用cout的,就可以同樣地使用這些類型的對象。? - 本節剩下部分所介紹的標準庫流特性都可以無差別地應用于普通流、文件流和string流,以及char或寬字符流版本
?8.1.1? IO對象無拷貝或賦值
- 如我們在7.1.3節(第234頁)所見,我們不能拷貝或對IO對象賦值:
- ofstream outl,out2;
- outl=out2;//錯誤:不能對流對象賦值
- ofstream print(ofstream);//錯誤:不能初始化ofstream參數
- out2=print(out2);//錯誤:不能拷貝流對象
- 由于不能拷貝IO對象,因此我們也不能將形參或返回類型設置為流類型(參見6.2.1節,第188頁)。進行IO操作的函數通常以引用方式傳遞和返回流。讀寫一個IO對象會改變其狀態,因此傳遞和返回的引用不能是const的。
8.1.2條件狀態
- IO操作一個與生俱來的問題是可能發生錯誤。一些錯誤是可恢復的,而其他錯誤則發生在系統深處,已經超出了應用程序可以修正的范圍。表8.2列出了IO類所定義的一些函數和標志,可以幫助我們訪問和操縱流的條件狀態(conditionstate)
- 下面是一個IO錯誤的例子:int ival;cin >> ival;
- 如果我們在標準輸入上鍵入Boo,讀操作就會失敗。代碼中的輸入運算符期待讀取一個int,但卻得到了一個字符B。這樣,cin會進入錯誤狀態。類似的,如果我們輸入一個文件結束標識,cin也會進入錯誤狀態。一個流一旦發生錯誤,其上后續的IO操作都會失敗。只有當一個流處于無錯狀態時,我們才可以從它讀取數據,向它寫入數據。由于流可能處于錯誤狀態,因此代碼通常應該在使用一個流之前檢查它是否處于良好狀態。確定一個流對象的狀態的最簡單的方法是將它當作一個條件來使用:
- while(cin >> word)//ok:讀操作成功...
- while循環檢查>>表達式返回的流的狀態。如果輸入操作成功,流保持有效狀態,則條件為真。
查詢流的狀態
- 將流作為條件使用,只能告訴我們流是否有效,而無法告訴我們具體發生了什么。有時我們也需要知道流為什么失敗。例如,在鍵入文件結束標識后我們的應對措施,可能與遇到一個IO設備錯誤的處理方式是不同的。
- IO庫定義了一個與機器無關的iostate類型,它提供了表達流狀態的完整功能。這個類型應作為一個位集合來使用,使用方式與我們在4.8節中(第137頁)使用quizl的方式一樣。IO庫定義了4個iostate類型的const expr值(參見2.4.4節,第58頁),表示特定的位模式。這些值用來表示特定類型的IO條件,可以與位運算符(參見4.8節,第137頁)一起使用來一次性檢測或設置多個標志位。
- badbit表示系統級錯誤,如不可恢復的讀寫錯誤。通常情況下,一旦badbit被置位,流就無法再使用了。在發生可恢復錯誤后,failbit被置位,如期望讀取數值卻讀出一個字符等錯誤。這種問題通常是可以修正的,流還可以繼續使用。如果到達文件結束位置,eofbit和failbit都會被置位。goodbit的值為0,表示流未發生錯誤。如果badbit、failbit和eofbit任一個被置位,則檢測流狀態的條件會失敗。
- 標準庫還定義了一組函數來查詢這些標志位的狀態。操作good在所有錯誤位均未置位的情況下返回true,而bad、fail和eof則在對應錯誤位被置位時返回true。此外,在badbit被置位時,fail也會返回true。這意味著,使用good或fail是確定流的總體狀態的正確方法。實際上,我們將流當作條件使用的代碼就等價于!fail()而eof和bad操作只能表示特定的錯誤。
管理條件狀態
- 流對象的rdstate成員返回一個iostate值,對應流的當前狀態。setstate操作將給定條件位置位,表示發生了對應錯誤。clear成員是一個重載的成員(參見6.4節,第206頁):它有一個不接受參數的版本,而另一個版本接受一個iostate類型的參數。clear不接受參數的版本清除(復位)所有錯誤標志位。執行clear()后,調用good會返回true。我們可以這樣使用這些成員:
- //記住cin的當前狀態
- auto old_state=cin.rdstate();//記住cin的當前狀態
- cin.clear();//使cin有效
- process_input(cin);//使用cin
- cin.setstate(old_state);//將cin置為原有狀態
- 帶參數的clear版本接受一個iostate值,表示流的新狀態。為了復位單一的條件狀態位,我們首先用rdstate讀出當前條件狀態,然后用位操作將所需位復位來生成新的狀態。例如,下面的代碼將failbit和badbit復位,但保持eofbit不變:
- //復位failbit和badbit,保持其他標志位不變
- cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
?
8 .1 .3 管理輸出緩沖
- 每個輸出流都管理一個緩沖區,用來保存程序讀寫的數據。例如,如果執行下面的代碼
- os << "please enter a value: **;
- 文本串可能立即打印出來,但也有可能被操作系統保存在緩沖區中,隨后再打印。有了緩沖機制,操作系統就可以將程序的多個輸出操作組合成單一的系統級寫操作。由于設備的寫操作可能很耗時,允許操作系統將多個輸出操作組合為單一的設備寫操作可以帶來很大的性能提升。緩沖區只有被數據填滿,才會刷新緩沖區,將數據寫到指定的文件中。
- 導致緩沖刷新(即,數據真正寫到輸出設備或文件)的原因有很多:
- 1,程序正常結束,作為main函數的return操作的一部分,緩沖刷新被執行
- 2,緩沖區滿時,需要刷新緩沖,而后新的數據才能繼續寫入緩沖區。
- 3,我們可以使用操縱符如endl(參見1.2節,第6頁)來顯式刷新緩沖區。
- 4,在每個輸出操作之后,我們可以用操縱符unitbuf設置流的內部狀態,來清空緩沖區。默認情況下,對cerr是設置unitbuf的,因此寫到cerr的內容都是立即刷新的。
- 5,一個輸出流可能被關聯到另一個流。在這種情況下,當讀寫被關聯的流時,關聯到的流的緩沖區會被刷新。例如,默認情況下,cin和cerr都關聯到cout。因此,讀cin或寫cerr都會導致cout的緩沖區被刷新。
刷新輸出緩沖區
- 我們己經使用過操縱符endl,它完成換行并刷新緩沖區的工作。IO庫中還有兩個類似的操縱符:flush和ends。flush刷新緩沖區,但不輸出任何額外的字符;ends向緩沖區插入一個空字符,然后刷新緩沖區:
- cout<<"hi!”<<endl;//輸出hi和一個換行,然后刷新緩沖區
- cout<<“hi!”<<flush;//輸出hi,然后刷新緩沖區,不附加任何額外字符
- cout<<"hi!"<<ends;//輸出hi和一個空字符,然后刷新緩沖區
unitbuf操縱符
- 如果想在每次輸出操作后都刷新緩沖區,我們可以使用unitbuf操縱符。它告訴流在接下來的每次寫操作之后都進行一次flush操作。而nounitbuf操縱符則重置流,使其恢復使用正常的系統管理的緩沖區刷新機制:
- cout<<unitbuf;//所有輸出操作后都會立即刷新緩沖區
- //任何輸出都立即刷新,無緩沖
- cout<<nounitbuf;//回到正常的緩沖方式
警告:如果程序崩潰,輸出緩沖區不會被刷新
- 如果程序異常終止,輸出緩沖區是不會被刷新的,,當一個程序崩潰后,它所輸出的數據很可能停留在輸出緩沖區中等待打印。
- 當調試一個已經崩潰的程序時,需要確認那些你認為已經輸出的數據確實已經刷新了。否則,可能將大量時間浪費在追蹤代碼為什么沒有執行上,而實際上代碼已經執行了,只是程序崩潰后緩沖區沒有被刷新,輸出數據被掛起沒有打印而已
關聯輸入和輸出流
- 當一個輸入流被關聯到一個輸出流時,任何試圖從輸入流讀取數據的操作都會先刷新關聯的輸出流。標準庫將cout和cin關聯在一起,因此下面語句
- cin>>ival;導致cout的緩沖區被刷新。
- 交互式系統通常應該關聯輸入流和輸出流。這意味著所有輸出,包括用戶提示信息,都會在讀操作之前被打印出來
- tie有兩個重載的版本(參見6.4節,第206頁):一個版本不帶參數,返回指向輸出流的指針。如果本對象當前關聯到一個輸出流,則返回的就是指向這個流的指針,如果
對象未關聯到流,則返回空指針。 - tie的第二個版本接受一個指向。stream的指針,將自己關聯到此ostreamo即,x.tie(&o)將流x關聯到輸出流O.我們既可以將一個istream對象關聯到另一個ostream,也可以將一個ostream關聯到另一個ostream:
- cin.tie(&cout);//僅僅是用來展示:標準庫將cin和cout關聯在一起
- //old_tie指向當前關聯到cin的流(如果有的話)
- ostream*old_tie=cin.tie(nullptr);//cin不再與其他流關聯
- //將cin與cerr關聯;這不是一個好主意,因為cin應該關聯到cout
- cin.tie(&cerr);//讀取cin會刷新cerr而不是cout
- cin.tie(old_tie);//重建cin和cout間的正常關聯
- 在這段代碼中,為了將一個給定的流關聯到一個新的輸出流,我們將新流的指針傳遞給了tie。為了徹底解開流的關聯,我們傳遞了一個空指針。每個流同時最多關聯到一個流,但多個流可以同時關聯到同一個ostream。