在C++ 中引入了流的概念,我們很方便的通過流來讀寫文本數據和二進制數據,那么流對象的數據究竟是怎么存儲的呢,為了搞清這個問題,先來看一看c++ 的 io 體系:
由圖可以看出,在stream 的實現中,除了虛基類IOS_BASE之外,所有的類內部都有一個streambuf, streambuf 是一個虛基類(不能被實例化,因此所內部包含streambuf(這個虛基類而非其子類)的類也是虛基類),代表流對象內部的緩沖區,就是我們流操作中輸入輸出的內容在內存中的緩沖區。
Streambuf有兩個子類,分別是stringbuf 和 filebuf,這兩個子類可以被實例化,我們常用的文件流和字符串流,內部的緩沖區就是這兩個類。
我們平常使用到的流基本是標准輸入輸出流,文件流和字符串流。在每個流初始化的時候都會初始化相應的streambuf(其實是它的子類)用來緩沖數據。
當我們用文件或者字符串初始化流的時候,流內部會保存該文件和字符串的信息,而在內部實例化一個streambuf用來緩沖數據,些數據時,當緩沖區滿的時候再將數據寫到文件或者字符串,讀數據時當緩沖區沒有數據時從文件或字符串讀數據到緩沖區。
在文件流這種情況下,streambuf 是為了避免大量的IO 操作
在字符串流的情況下,streambuf (其實是套在上面的流對象)是為了提供字符串的格式化讀取和輸出操作(想象字符串是你從鍵盤輸入的數據)
所以streambuf 可以看作一塊緩沖區,用來存儲數據,在這種情況下,我們常常在程序中用的 char數組緩沖區是不是可以被替代呢?答案是of course
而且,有了streambuf ,緩沖區的管理和寫入寫出都非常方便,最好的是流對象有復制拷貝等構造函數可以方便參數傳遞等需要拷貝的情景。
但是streambuf 本身是個虛基類,不能實例化,所以要用streambuf 就需要自己繼承streambuf 寫一個新的類出來才能用,這個實現方法最后介紹,好在c++ 標准類庫實現了兩個子類stringbuf 和 filebuf ,所以我們可以選stringbuf 來作為我們的數據緩沖對象(不選filebuf 是因為它的實現和文件緊耦合的,只適合文件流)
流對象有一個構造函數是通過streambuf 來構造:
-
stringbuf sb;
-
istream is(&sb);
有了流對象我們就可以在流上進行各種輸入輸出操作,輸入會從緩沖區讀數據,輸出會將數據寫到緩沖區
注意對緩沖區的讀寫一定要注意方法,流符號是格式話輸入輸出,get,put,read,write等是二進制讀寫。
格式化輸入的內容應當格式化讀取,二進制寫入應當二進制讀取否則會出現寫入和讀出數據不一致的問題
格式化寫入一個int 數據時,會將該數據每位分離出來,按照字符編碼寫到緩沖區,例如 int x= 123, 格式化寫入以后緩沖區存以后,緩沖區有三個字節分別存放1,2,3的字符編碼。格式化讀出是相反的過程,將讀到的字符轉成相應的類型的數據
二進制寫入時進行直接的內存拷貝不做任何動作,例如 int x = 123 二進制寫入后(二進制寫時需要取地址,轉成char* 並指出要寫入的字節數,如f.write((char*)&x,sizeof(int))
寫完后緩沖區的數據是0x0000007b,是計算機內存中對123 的內存的完全拷貝
下面是緩沖區使用的情景:
考慮一個生產者,消費者的問題,線程A 生成的數據,線程B讀取,可以解決的方案如下:
1. 設立全局變量,緩沖數據,A,B都可以訪問(在這種情況下,A 生產的時候要考慮緩沖區是否夠用,B讀取的時候要判斷當前是否有有效數據可讀,而且很難設計一個合理分配內存的緩沖區(想象A生產的數據有時很大,有時很小))
2.網絡通信(TCP,UDP)
3. streambuf 登場,有了streambuf配合stream, A就像正常操作流一樣往流對象里塞數據,而B 就像正常操作流一樣從流里面讀數據,不用關心其他問題,只要這兩個流的sterambuf 是同一個對象。
上一段代碼:
————————————————
#include <iostream> #include <streambuf> #include <sstream> #include <fstream> #include <string> #include <cstring> #include <memory> #include <thread> using namespace std; stringbuf buf; istream in(&buf); ostream out(&buf); bool flag = false; void threadb() { char data; while (true) { if (flag) { in >> data; cout << "thread B recv:" << data << endl; flag = false; } } } int main() { thread consumer(threadb); char data; while (true) { cin >> data; out << data; flag = true; } return 0; }
在特殊的情景下可以實現自己的streambuf類,自己實現的類必須繼承streambuf 類,自定義的streambuf 必須實現overflow,underflow,uflow 等方法,其中overflow在輸出緩沖區不夠用時調用,underflow和uflow在輸入緩沖區無數據時調用,區別是uflow 會讓讀取位置前進一位,而underflow不會。sreambuf 內部維護着六個指針 eback,gptr,egptr,pbase,pptr,epptr.分別指向讀取緩沖區的頭,當前讀取位置,尾,寫緩沖區的頭,當前寫位置,尾(實際上這幾個指針指向同一段緩沖區)
自定義實現方式要注意要在該返回EOF的時候,返回EOF,underflow和uflow都有可能返回EOF,一旦返回了EOF則標志着流結束,之后對流的操作無效。
如下代碼實現了一個自定義的streambuf:
#include <iostream> #include <streambuf> #include <sstream> #include <fstream> #include <string> #include <cstring> #include <memory> using namespace std; class mybuf : public streambuf { public: enum{ SIZE = 10}; mybuf() { memset(buffer, 'j', 10); //buffer[3] = ' '; setbuf(buffer, SIZE); } void log() { cout <<hex<<gptr() << endl; } protected: int_type overflow( int_type c) { cout << "overflow" << endl; return c; } streambuf* setbuf(char* s, streamsize n) { setp(s, s + n); setg(s, s, s + n); return this; } int_type underflow() override{ cout << "here"<<endl; memset(buffer, 'w', 10); setg(buffer, buffer, buffer+10); return ' '; } int_type uflow() override{ cout << "uflow" << endl; memset(buffer, 'x', 10); setg(buffer, buffer, buffer + 10); return EOF; } private: char buffer[SIZE]; }; int main() { mybuf buf; char test[2000]; memset(test, 'a', 2000); //buf.pubsetbuf(test, 1000); string hh; string xx; istream in(&buf); ostream tt(&buf); in>>hh; cout << hh << endl; //tt.write(test, 9); in >> xx; in.read(test, 11); cout<< xx << endl; cout << "end" << endl; return 0; }