《C++ Primer 5th》笔记(8 / 19):IO库
文章目錄
- IO類
- IO類型間的關系
- IO對象無拷貝或賦值
- 條件狀態
- 查詢流的狀態
- 管理條件狀態
- 管理輸出緩沖
- 刷新輸出緩沖區
- unitbuf操縱符號
- 警告:如果程序崩潰,輸出緩沖區不會被刷新
- 關聯輸入和輸出流
- 文件輸入輸出
- 使用文件流對象
- 用fstream代替iostream&
- 成員函數open和close
- 自動構造和析構
- 文件模式
- 以out模式打開文件會丟失已有數據
- 每次調用open時都會確定文件模式
- string流
- 使用istringstream
- 使用ostringstream
前面用到的IO庫設施:
- istream(輸入流)類型,提供輸入操作。
- ostream(輸出流)類型,提供輸出操作。
- cin,一個istream對象,從標準輸入讀取數據。
- cout,一個ostream對象,向標準輸出寫入數據。
- cerr,一個ostream對象,通常用于輸出程序錯誤消息,寫入到標準錯誤。
- >>運算符,用來從一個istream對象讀取輸入數據。
- <<運算符,用來向一個ostream對象寫入輸出數據。
- getline函數,從一個給定的istream讀取一行數據,存入一個給定的string對象中。
IO類
到目前為止,我們已經使用過的IO類型和對象都是操縱char 數據的。默認情況下,這些對象都是關聯到用戶的控制臺窗口的。當然,我們不能限制實際應用程序僅從控制臺窗口進行IO操作,應用程序常常需要讀寫命名文件。而且,使用IO操作處理string中的字符會很方便。此外,應用程序還可能讀寫需要寬字符支持的語言。
為了支持這些不同種類的IO處理操作,在istream和ostream之外,標準庫還定義了其他一些IO類型,我們之前都已經使用過了。
下表列出了這些類型,分別定義在三個獨立的頭文件中:
- iostream定義了用于讀寫流的基本類型,
- fstream定義了讀寫命名文件的類型,
- sstream定義了讀寫內存string對象的類型。
| iostream | istream,wistream從流讀取數據 ostream,wostream向流寫入數據 iostream,wiostream讀寫流 |
| fstream | ifstream,wifstream從文件讀取數據 ofstream,wofstream向文件寫入數據 fstream,wfstream讀寫文件 |
| sstream | istringstream,wistringstream從string讀取數據 ostringstream,wostringstream向string寫入數據 stringstream,wstringstream讀寫string |
為了支持使用寬字符的語言,標準庫定義了一組類型和對象來操縱wchar_t類型的數據。寬字符版本的類型和函數的名字以一個w開始。例如,wcin、wcout和 wcerr是分別對應cin、cout和 cerr的寬字符版對象。
寬字符版本的類型和對象與其對應的普通char版本的類型定義在同一個頭文件中。例如,頭文件fstream定義了ifstream和 wifstream類型。
C++提供了幾種字符類型,其中多數支持國際化。基本的字符類型是char,一個 char的空間應確保可以存放機器基本字符集中任意字符對應的數字值。也就是說,一個char的大小和一個機器字節一樣。
其他字符類型用于擴展字符集,如 wchar_t、char16_t、char32_t。wchar_t類型用于確保可以存放機器最大擴展字符集中的任意一個字符,類型 charl6_t和char32_t則為Unicode字符集服務(Unicode是用于表示所有自然語言中字符的標準)。
IO類型間的關系
概念上,設備類型和字符大小都不會影響我們要執行的IO操作。例如,我們可以用>>讀取數據,而不用管是從一個控制臺窗口,一個磁盤文件,還是一個string 讀取。類似的,我們也不用管讀取的字符能存入一個char對象內,還是需要一個wchar_t對象來存儲。
標準庫使我們能忽略這些不同類型的流之間的差異,這是通過繼承機制(inheritance)實現的。利用模板,我們可以使用具有繼承關系的類,而不必了解繼承機制如何工作的細節。將在第15、18章和介紹C++是如何支持繼承機制的。
簡單地說,繼承機制使我們可以聲明一個特定的類繼承自另一個類。我們通常可以將一個派生類(繼承類)對象當作其基類(所繼承的類)對象來使用。
類型ifstream和istringstream都繼承自istream。因此,我們可以像使用istream對象一樣來使用ifstream和istringstream對象。也就是說,我們是如何使用cin的,就可以同樣地使用這些類型的對象。
例如,可以對一個ifstream或istringstream對象調用getline,也可以使用>>從一個 ifstream或istringstream對象中讀取數據。
類似的,類型ofstream和ostringstream都繼承自ostream。因此,我們是如何使用cout的,就可以同樣地使用這些類型的對象。
Note:本節剩下部分所介紹的標準庫流特性都可以無差別地應用于普通流、文件流和string流,以及char或寬字符流版本。
IO對象無拷貝或賦值
我們不能拷貝或對IO對象賦值:
ofstream out1, out2; out1 = out2; //錯誤:不能對流對象賦值 ofstream print(ofstream); //錯誤:不能初始化ofstream參數 out2 = print(out2); //錯誤:不能拷貝流對象由于不能拷貝IO對象,因此我們也不能將形參或返回類型設置為流類。
進行IO操作的函數通常以引用方式傳遞和返回流。讀寫一個IO對象會改變其狀態,因此傳遞和返回的引用不能是const的。
條件狀態
IO操作一個與生俱來的問題是可能發生錯誤。一些錯誤是可恢復的,而其他錯誤則發生在系統深處,已經超出了應用程序可以修正的范圍。
下表列出了IO類所定義的一些函數和標志,可以幫助我們訪問和操縱流的條件狀態(condition state)。
| strm::iostate | strm是一種IO類型,就是下面4條狀態。iostate是一種機器相關的類型,提供了表達條件狀態的完整功能 |
| strm::badbit | strm::badbit用來指出流已崩潰 |
| strm::failbit | strm::failbit用來指出一個IO操作失敗了 |
| strm::eofbit | strm::eofbit用來指出流到達了文件結束 |
| strm::goodbit | strm::goodbit用來指出流未處于錯誤狀態。此值保證為零 |
| s.eof() | 若流s的eofbit置位,則返回true |
| s.fail() | 若流s的failbit或badbit置位,則返回true |
| s.bad() | 若流s的badbit置位,則返回true |
| s.good() | 若流s處于有效狀態,則返回true |
| s.clear() | 將流s中所有條件狀態位復位,將流的狀態設置為有效。返回void |
| s.clear(flags) | 根據給定的flags標志位,將流s 中對應條件狀態位復位。flags的類型為strm : :iostate。返回void |
| s.setstate(flags) | 根據給定的flags標志位,將流s中對應條件狀態位置位。flags的類型為strm::iostate。返回void |
| s.rdstate() | 返回流s的當前條件狀態,返回值類型為strm::iostate |
下面是一個IO錯誤的例子:
int ival; cin >> ival;如果我們在標準輸入上鍵入 Boo,讀操作就會失敗。代碼中的輸入運算符期待讀取一個int,但卻得到了一個字符B。這樣,cin 會進入錯誤狀態。類似的,如果我們輸入一個文件結束標識,cin也會進入錯誤狀態。
一個流一旦發生錯誤,其上后續的IO操作都會失敗。只有當一個流處于無錯狀態時,我們才可以從它讀取數據,向它寫入數據。由于流可能處于錯誤狀態,因此代碼通常應該在使用一個流之前檢查它是否處于良好狀態。
確定一個流對象的狀態的最簡單的方法是將它當作一個條件來使用:
while (cin >> word)//ok:讀操作成功……while循環檢查>>表達式返回的流的狀態。如果輸入操作成功,流保持有效狀態,則條件為真。
查詢流的狀態
將流作為條件使用,只能告訴我們流是否有效,而無法告訴我們具體發生了什么。有時我們也需要知道流為什么失敗。例如,在鍵入文件結束標識后我們的應對措施,可能與遇到一個IO設備錯誤的處理方式是不同的。
IO庫定義了一個與機器無關的iostate類型,它提供了表達流狀態的完整功能。這個類型應作為一個位集合來使用,使用方式就是位運算(左移、右移、與或非)。IO庫定義了4個iostate類型的constexpr值來表示特定的位模式。這些值用來表示特定類型的IO條件,可以與位運算符一起使用來一次性檢測或設置多個標志位。
(4種狀態)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成員是一個重載的成員:它有一個不接受參數的版本,而另一個版本接受一個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);管理輸出緩沖
每個輸出流都管理一個緩沖區,用來保存程序讀寫的數據。例如,如果執行下面的代碼
os << "please enter a value: ";文本串可能立即打印出來,但也有可能被操作系統保存在緩沖區中,隨后再打印。有了緩沖機制,操作系統就可以將程序的多個輸出操作組合成單一的系統級寫操作。由于設備的寫操作可能很耗時,允許操作系統將多個輸出操作組合為單一的設備寫操作可以帶來很大的性能提升。
導致緩沖刷新(即,數據真正寫到輸出設備或文件)的原因有很多:
- 程序正常結束,作為main函數的return操作的一部分,緩沖刷新被執行。
- 緩沖區滿時,需要刷新緩沖,而后新的數據才能繼續寫入緩沖區。
- 可以使用操縱符如endl來顯式刷新緩沖區。
- 在每個輸出操作之后,我們可以用操縱符unitbuf設置流的內部狀態,來清空緩沖區。默認情況下,對cerr是設置unitbuf 的,因此寫到cerr的內容都是立即刷新的。
- 一個輸出流可能被關聯到另一個流。在這種情況下,當讀寫被關聯的流時,關聯到的流的緩沖區會被刷新。例如,默認情況下,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的緩沖區被刷新。
Note:交互式系統通常應該關聯輸入流和輸出流。這意味著所有輸出,包括用戶提示信息,都會在讀操作之前被打印出來。
tie有兩個重載的版本:
-
第一個版本不帶參數,返回指向輸出流的指針。
- 如果本對象當前關聯到一個輸出流,則返回的就是指向這個流的指針,
- 如果對象未關聯到流,則返回空指針。
-
第二個版本接受一個指向ostream的指針,將自己關聯到此ostream。即,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。
文件輸入輸出
頭文件 fstream定義了三個類型來支持文件IO:
- ifstream從一個給定文件讀取數據,
- ofstream向一個給定文件寫入數據,
- fstream可以讀寫給定文件。
在第17章我們將介紹如何對同一個文件流既讀又寫。
這些類型提供的操作與我們之前已經使用過的對象cin和cout的操作一樣。特別是,我們可以用IO運算符(<<和>>)來讀寫文件,可以用getline從一個ifstream讀取數據,包括前面介紹的內容也都適用于這些類型。
除了繼承自iostream類型的行為之外,fstream中定義的類型還增加了一些新的成員來管理與流關聯的文件。
在下表中列出了這些操作,我們可以對fstream、ifstream和 ofstream對象調用這些操作,但不能對其他IO類型調用這些操作。
| fstream fstrm; | 創建一個未綁定的文件流。fstream是頭文件fstream中定義的一個類型 |
| fstream fstrm(s); | 創建一個fstream,并打開名為s的文件。s可以是string類型,或者是一個指向C風格字符串的指針。這些構造函數都是explicit的。默認的文件模式mode依賴于fstream的類型 |
| fstream fstrm(s, mode); | 與前一個構造函數類似,但按指定mode打開文件 |
| fstrm.open(s) | 打開名為s的文件,并將文件與 fstrm綁定。s可以是一個string或一個指向C風格字符串的指針。默認的文件mode依賴于fstream的類型。返回void |
| fstrm.close() | 關閉與fstrm綁定的文件。返回void |
| fstrm.is_open() | 返回一個bool值,指出與fstrm關聯的文件是否成功打開且尚未關閉 |
使用文件流對象
當我們想要讀寫一個文件時,可以定義一個文件流對象,并將對象與文件關聯起來。每個文件流類都定義了一個名為open的成員函數,它完成一些系統相關的操作,來定位給定的文件,并視情況打開為讀或寫模式。
創建文件流對象時,我們可以提供文件名(可選的)。如果提供了一個文件名,則open會自動被調用:
ifstream in(ifile);//構造一個 ifstream并打開給定文件 ofstream out;//輸出文件流未關聯到任何文件這段代碼定義了一個輸入流in,它被初始化為從文件讀取數據,文件名由 string類型的參數ifile指定。第二條語句定義了一個輸出流out,未與任何文件關聯。在新C++標準中,文件名既可以是庫類型string對象,也可以是C風格字符數組。舊版本的標準庫只允許C風格字符數組。
用fstream代替iostream&
前文已經提到過,在要求使用基類型對象的地方,我們可以用繼承類型的對象來替代。這意味著,接受一個 iostream類型引用(或指針)參數的函數,可以用一個對應的fstream(或sstream)類型來調用。也就是說,如果有一個函數接受一個ostream&參數,我們在調用這個函數時,可以傳遞給它一個ofstream對象,對istream&和 ifstream也是類似的。
類型ifstream和istringstream都繼承自istream。因此,我們可以像使用istream對象一樣來使用ifstream和istringstream對象。也就是說,我們是如何使用cin的,就可以同樣地使用這些類型的對象。
例如,可以對一個ifstream或istringstream對象調用getline,也可以使用>>從一個 ifstream或istringstream對象中讀取數據。
類似的,類型ofstream和ostringstream都繼承自ostream。因此,我們是如何使用cout的,就可以同樣地使用這些類型的對象
例如,可以用第7章中的read和print函數來讀寫命名文件。在本例中,我們假定輸入和輸出文件的名字是通過傳遞給main函數的參數來指定的:
// input transactions contain ISBN, number of copies sold, and sales price istream &read(istream &is, Sales_data &item) {double price = 0;is >> item.bookNo >> item.units_sold >> price;item.revenue = price * item.units_sold;return is; }ostream &print(ostream &os, const Sales_data &item) {os << item.isbn() << " " << item.units_sold << " "<< item.revenue << " " << item.avg_price();return os; } ifstream input(argv[1]); // open the file of sales transactions ofstream output(argv[2]); // open the output file Sales_data total; // variable to hold the running sum if (read(input, total)) { // read the first transactionSales_data trans; // variable to hold data for the next transactionwhile(read(input, trans)) { // read the remaining transactionsif (total.isbn() == trans.isbn()) // check isbnstotal.combine(trans); // update the running totalelse {print(output, total) << endl; // print the resultstotal = trans; // process the next book}}print(output, total) << endl; // print the last transaction } else // there was no inputcerr << "No data?!" << endl;重要的部分是對 read 和 print 的調用。雖然兩個函數定義時指定的形參分別是 istream&和ostream&,但我們可以向它們傳遞fstream對象。
成員函數open和close
如果我們定義了一個空文件流對象,可以隨后調用open來將它與文件關聯起來:
ifstream in(ifile); // construct an ifstreamand open the given file ofstream out; // output file stream that is not associated with any file out.open(ifile + ".copy"); // open the specified file如果調用open失敗,failbit會被置位。因為調用open可能失敗,進行open是否成功的檢測通常是一個好習慣:
if(out) //檢查open是否成功//open成功,我們可以使用文件了這個條件判斷與我們之前將cin用作條件相似。如果open失敗,條件會為假,我們就不會去使用out了。
一旦一個文件流已經打開,它就保持與對應文件的關聯。實際上,對一個已經打開的文件流調用open會失敗,并會導致failbit被置位。隨后的試圖使用文件流的操作都會失敗。為了將文件流關聯到另外一個文件,必須首先關閉已經關聯的文件。一旦文件成功關閉,我們可以打開新的文件:
in.close() ;//關閉文件 in.open(ifile +"2");//打開另一個文件如果open成功,則open會設置流的狀態,使得good()為 true。
自動構造和析構
考慮這樣一個程序,它的 main函數接受一個要處理的文件列表。這種程序可能會有如下的循環:
// for each file passed to the program for (auto p = argv + 1; p != argv + argc; ++p) {ifstream input(*p); // create input and open the fileif (input) { // if the file is ok, ''process'' this fileprocess(input);} elsecerr << "couldn't open: " + string(*p); } // input goes out of scope and is destroyed on each iteration每個循環步構造一個新的名為input的ifstream對象,并打開它來讀取給定的文件。像之前一樣,我們檢查open是否成功。如果成功,將文件傳遞給一個函數,該函數負責讀取并處理輸入數據。如果open失敗,打印一條錯誤信息并繼續處理下一個文件。
因為input是while循環的局部變量,它在每個循環步中都要創建和銷毀一次。當一個fstream對象離開其作用域時,與之關聯的文件會自動關閉。在下一步循環中,input會再次被創建。
Note:當一個fstream對象被銷毀時,close會自動被調用。
文件模式
每個流都有一個關聯的文件模式(file mode),用來指出如何使用文件。下表列出了文件模式和它們的含義。
| in | 以讀方式打開 |
| out | 以寫方式打開 |
| app | 每次寫操作前均定位到文件末尾 |
| ate | 打開文件后立即定位到文件末尾 |
| trunc | 截斷文件(如果打開的文件存在,其內容將被丟棄,其大小被截斷為零link) |
| binary | 以二進制方式進行IO |
無論用哪種方式打開文件,我們都可以指定文件模式,調用open打開文件時可以,用一個文件名初始化流來隱式打開文件時也可以。指定文件模式有如下限制:
- 只可以對ofstream或fstream對象設定out模式。
- 只可以對ifstream或fstream對象設定in模式。
- 只有當out也被設定時才可設定trunc模式。
- 只要trunc沒被設定,就可以設定app模式。在app模式下,即使沒有顯式指定out模式,文件也總是以輸出方式被打開。
- 默認情況下,即使我們沒有指定trunc,以out模式打開的文件也會被截斷(即清空文件內容)。為了保留以out模式打開的文件的內容,我們必須同時指定app模式,這樣只會將數據追加寫到文件末尾;或者同時指定in模式,即打開文件同時進行讀寫操作(第17章將介紹對同一個文件既進行輸入又進行輸出的方法)。
- ate和binary模式可用于任何類型的文件流對象,且可以與其他任何文件模式組合使用。
每個文件流類型都定義了一個默認的文件模式,當我們未指定文件模式時,就使用此默認模式。
- 與 ifstream關聯的文件默認以in模式打開;
- 與ofstream關聯的文件默認以out模式打開;
- 與fstream關聯的文件默認以in和out模式打開。
以out模式打開文件會丟失已有數據
默認情況下,當我們打開一個ofstream時,文件的內容會被丟棄。阻止一個ofstream清空給定文件內容的方法是同時指定app模式:
// file1 is truncated in each of these cases ofstream out("file1"); // out and trunc are implicit ofstream out2("file1", ofstream::out); // trunc is implicit ofstream out3("file1", ofstream::out | ofstream::trunc);// to preserve the file's contents, we must explicitly specify app mode ofstream app("file2", ofstream::app); // out is implicit ofstream app2("file2", ofstream::out | ofstream::app);WARNING:保留被ofstream打開的文件中已有數據的唯一方法是顯式指定 app或in模式。
每次調用open時都會確定文件模式
對于一個給定流,每當打開文件時,都可以改變其文件模式。
ofstream out; //未指定文件打開模式 out.open("scratchpad");//模式隱含設置為輸出和截斷 out.close(); //關閉out,以便我們將其用于其他文件out.open("precious", ofstream::app) ; //模式為輸出和追加 out.close();第一個open調用未顯式指定輸出模式,文件隱式地以out模式打開。
通常情況下,out模式意味著同時使用trunc模式。因此,當前目錄下名為scratchpad的文件的內容將被清空。
當打開名為 precious 的文件時,我們指定了append模式。文件中已有的數據都得以保留,所有寫操作都在文件末尾進行。
Note:在每次打開文件時,都要設置文件模式,可能是顯式地設置,也可能是隱式地設置。當程序未指定模式時,就使用默認值。
(MyNote:我們可以用IO運算符(<<和>>)來讀寫文件,可以用getline從一個ifstream讀取數據。)
string流
sstream頭文件定義了三個類型來支持內存IO,這些類型可以向string寫入數據,從string讀取數據,就像string是一個IO流一樣。
- istringstream從string讀取數據,
- ostringstream向string寫入數據,
- 而頭文件stringstream 既可從string讀數據也可向string寫數據。
與fstream類型類似,頭文件sstream中定義的類型都繼承自我們已經使用過的iostream頭文件中定義的類型。除了繼承得來的操作,sstream 中定義的類型還增加了一些成員來管理與流相關聯的 string。
下表列出了這些操作,可以對stringstream對象調用這些操作,但不能對其他IO類型調用這些操作。
| sstream strm; | strm是一個未綁定的stringstream對象。sstream是頭文件sstream中定義的一個類型 |
| sstream strm(s); | strm是一個sstream對象,保存string s的一個拷貝。此構造函數是explicit的 |
| strm.str() | 返回strm所保存的string的拷貝 |
| strm.str(s) | 將string s拷貝到strm中。返回void |
使用istringstream
當我們的某些工作是對整行文本進行處理,而其他一些工作是處理行內的單個單詞時,通常可以使istringstream。
考慮這樣一個例子,假定有一個文件,列出了一些人和他們的電話號碼。某些人只有一個號碼,而另一些人則有多個——家庭電話、工作電話、移動電話等。我們的輸入文件看起來可能是這樣的:
morgan 2015552368 8625550123 drew 9735550130 lee 6095550132 2015550175 8005550000文件中每條記錄都以一個人名開始,后面跟隨一個或多個電話號碼。我們首先定義一個簡單的類來描述輸入數據:
// members are public by default; see § 7.2 (p. 268) struct PersonInfo {string name;vector<string> phones; };類型PersonInfo的對象會有一個成員來表示人名,還有一個vector來保存此人的所有電話號碼。
我們的程序會讀取數據文件,并創建一個PersonInfo的vector。vector中每個元素對應文件中的一條記錄。我們在一個循環中處理輸入數據,每個循環步讀取一條記錄,提取出一個人名和若干電話號碼:
string line, word; // will hold a line and word from input, respectively vector<PersonInfo> people; // will hold all the records from the input // read the input a line at a time until cin hits end-of-file (or another error) while (getline(cin, line)) {PersonInfo info; // create an object to hold this record's dataistringstream record(line); // bind record to the line we just readrecord >> info.name; // read the namewhile (record >> word) // read the phone numbersinfo.phones.push_back(word); // and store thempeople.push_back(info); // append this record to people }使用ostringstream
當我們逐步構造輸出,希望最后一起打印時,ostringstream是很有用的。例如,對上一節的例子,我們可能想逐個驗證電話號碼并改變其格式。如果所有號碼都是有效的,我們希望輸出一個新的文件,包含改變格式后的號碼。對于那些無效的號碼,我們不會將它們輸出到新文件中,而是打印一條包含人名和無效號碼的錯誤信息。
由于我們不希望輸出有無效電話號碼的人,因此對每個人,直到驗證完所有電話號碼后才可以進行輸出操作。但是,我們可以先將輸出內容”寫入”到一個內存ostringstream中:
for (const auto &entry : people) { // for each entry in peopleostringstream formatted, badNums; // objects created on each loopfor (const auto &nums : entry.phones) { // for each numberif (!valid(nums)) {badNums << " " << nums; // string in badNums} else// ''writes'' to formatted's stringformatted << " " << format(nums);}if (badNums.str().empty()) // there were no bad numbersos << entry.name << " " // print the name<< formatted.str() << endl; // and reformatted numberselse // otherwise, print the name and bad numberscerr << "input error: " << entry.name<< " invalid number(s) " << badNums.str() << endl; }在此程序中,我們假定已有兩個函數,valid和format,分別完成電話號碼驗證和改變格式的功能。
程序最有趣的部分是對字符串流formatted和 badNums 的使用。我們使用標準的輸出運算符(<<)向這些對象寫入數據,但這些“寫入”操作實際上轉換為string操作,分別向formatted和badNums中的string對象添加字符。
總結
以上是生活随笔為你收集整理的《C++ Primer 5th》笔记(8 / 19):IO库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Python Cookbook 3rd
- 下一篇: 《Python Cookbook 3rd