C++管理輸出緩衝區

2020-07-16 10:05:23
每個輸出流都管理一個緩衝區,用來儲存程式讀寫的資料。例如,如果執行下而的程式碼:

cout << "http://c.biancheng.net/cplus/";

文字串可能立即列印出來,但也有可能被作業系統儲存在緩衝區中,隨後再列印。

有了緩衝機制,作業系統就可以將程式的多個輸出操作組合成單一的系統級寫操作。由於裝置的寫操作可能很耗時,允許作業系統將多個輸出操作組合為單一的裝置寫操作可以帶來很大的效能提升。

導致緩衝重新整理(資料真正寫到輸出裝置或檔案)的原因有很多:
  • 程式正常結束,作為 main() 函數的 return 操作的一部分,緩衝重新整理被執行。
  • 緩衝區滿時,需要重新整理緩衝,而後新的資料才能繼續寫入緩衝區。
  • 我們可以使用操縱符如 endl 來顯式重新整理緩衝區。
  • 在每個輸出操作之後,我們可以用操作符 unitbuf 設定流的內部狀態,來清空緩衝區。預設情況下,對 cerr 是設定 unitbuf 的,因此寫到 cerr 的內容都是立即重新整理的。
  • 一個輸出流可能被關聯到另一個流。在這種情況下,當讀寫被關聯的流時,關聯到的流的緩衝區會被重新整理。例如,預設情況下,cin 和 cerr 都關聯到 cout。因此,讀 cin 或寫 cerr 都會導致 cout 的緩衝區被重新整理。

重新整理輸出緩衝區

我們已經使用過操作符 endl,它完成換行並重新整理緩衝區的工作。IO 庫中還有兩個類似的操縱符:
  • flush 和 ends。flush 重新整理緩衝區,但不輸出任何額外的字元;
  • ends向緩衝區插入一個空字元,然後重新整理緩衝區。

值得一提得是,cout 所屬 ostream 類中還提供有 flush() 成員方法,它和 flush 操縱符的功能完全一樣,僅在使用方法上( cout.flush() )有區別。

請看下面的例子:
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() 函數可以用來繫結輸出流,它有兩個過載的版本:

ostream* tie ( ) const;  //返回指向系結的輸出流的指標。
ostream* tie ( ostream* os );  //將 os 指向的輸出流系結的該物件上,並返回上一個系結的輸出流指標。

第一個版本不帶引數,返冋指向出流的指標。如果本物件當前關聯到一個輸出流,則返回的就是指向這個流的指標,如果物件未關聯到流,則返回空指標。

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。