streambuf類為緩沖區提供內存,並提供了用於填充緩沖區,訪問緩沖區,刷新新緩沖區和管理緩沖區內存的類方法。
ios_base類表示流的一般特征,如是否可讀,是二進制還是文本流等。
ios類基於ios_base,其中包括了一個之指向streambuf對象的的指針成員。
緩沖區介紹
輸入:從磁盤上讀取大量信息,將這些信息存儲在緩沖區中,然后每次從緩沖區讀取一個字節,到達緩沖區尾部,在從磁盤上讀取另一塊數據。
但是鍵盤每次輸入一個字符時可以用不緩沖區,但是用緩沖區輸入可以讓程序沒有傳給程序之前返回並改正,c++通常是用戶按下回車鍵刷新緩沖區,
輸出:程序首先填滿緩沖區,然后把整塊數據給磁盤,並清空緩沖區,已備下一批輸入。
對於輸出屏幕,輸出換行符時刷新緩沖區(也可能是其他:當輸入語句到達時,他刷新緩沖區中當前所有輸出)
刷新緩沖:用cout將字節發送給標准輸出時由於ostram類對cout對象輸出進行緩沖,輸出並不會直接發送到目標地址,而是在緩沖區中,直到緩沖區填滿,然后flush緩沖區,把內容發送出去並且清空緩沖,緩沖區通常為512byte或其整數倍。
對於屏幕來說,程序不必等到緩沖區填滿,1.將換行符發送到緩沖區時刷新緩沖區;2.輸入即將發生時刷新緩沖區。
由於程序運行時並不總是知道外部輸入的進度,很難控制是不是全部清除輸入緩沖區的內容。通常我們有可能只是希望放棄輸入緩沖區中的一部分,而不是全部。比如清除掉當前行、或者清除掉行尾的換行符等等。但要是緩沖區中已經有了下一行的內容,這部分可能是我們想保留的。這個時候最好不要用sync()。可以考慮用ignore函數代替
cin.ignore(numeric_limits<std::streamsize>::max(),'/n');//清除當前行 cin.ignore(numeric_limits<std::streamsize>::max()); //清除cin里所有內容
numeric_limits<std::streamsize>::max()不過是climits頭文件定義的流使用的最大值,你也可以用一個足夠大的整數代替它。使用ignore顯然能比sync()更精確控制緩沖區
cin.ignore(1024,'\n'),通常把第一個參數設置得足夠大,這樣實際上總是只有第二個參數'\n'起作用,所以這一句就是把回車(包括回車)之前的所以字符從輸入緩沖(流)中清除出去。
flush(cout); cout<<"hello word"<<flush; cout<<"hello word"<<endl;//刷新緩沖區並插入換行符
以上三種方式均可以刷新緩沖區
但cout<<'\n';只是輸出換行符,不刷新緩沖區;\n只代表換行的轉義字符;\n是C中間的格式輸出換行,C++保留了下來;輸出'\n'是實際輸出了的'\10',往輸出流里添加了信息,所有的字符都是'\xx'的形式,換行符也是,你用其它任何字符輸出一下,前面都會有四個'*'填充的
程序員常常在調試時添加打印語句。這類語句應該保證‘一直’刷新流。否則,如果程序崩潰,輸出可能還停留在緩沖區中,從而導致關於程序崩潰位置的錯誤推斷
緩沖區的操作https://liam.page/2017/12/31/buffer-of-stream-in-Cpp/ https://izualzhy.cn/stream-buffer
#include <iostream> #include <sstream> #include <cstdio> using namespace std; int main() { stringstream ss; streambuf *buf=cout.rdbuf(); //使用了新的緩沖區后,字符串不會輸出到屏幕,而是由stringstream管理 cout.rdbuf(ss.rdbuf()); cout<<"hello word"<<endl; printf("%s",ss.str().c_str()); cout.rdbuf(buf); cout<<"hello word"<<endl; return 0; }
基於控制台
繼承關系
c++將輸出看做字節流,將數值類型轉換為文本形式表示的字符流(將數據內部表示(二進制形式)轉換為字符字節組成的輸出流)
cout:對於void*類型,打印其地址數值(一端關聯顯示器另一端關聯程序)
cin:從非空白字符開始,到與目標類型不匹配的第一個字符之間的全部內容(一端關聯鍵盤另一端關聯程序)
cerr:關聯輸出設備(通常為顯示器)沒有緩沖區,信息直接輸出到屏幕,不會等到新的換行或緩沖區滿
clog:關聯輸出設備(通常為顯示器)有緩沖區
ostream:ostream &<<使用該操作符返回一個指向該對象的引用,插入操作符的返回值是調用該操作符的對象
int a=0,sum=0; while(cin>>a) sum+=a; //輸入1 2 3 4 5 6c 60
由於輸入緩沖,在用戶鍵入回車之前不會發送給程序,但是循環在了對字符c出停止了處理,導致cin>>a返回alse,終止循環
狀態流
cin或cout對象包含一個描述狀態流的數據成員(繼承於ios_base)被定義為iostate,是bitmask類型,由eofbit,badbit,failbit,每一位可以是1(設置)0(清除)
clear:將狀態設置為他的參數,默認0清楚全部狀態;clear(eofbit),eofbit被設置,其他清除
setstate:setstate(eofbit)只設置eofbit,不影響其他位
get()與getline():get將換行符留在流中,getline抽取並丟棄輸入流中的換行符
char tem[60]; while(cin.get(tem,60))//空行終止 { //... } //空行不終止,因為空行不導致getline設置failbit,getline抽取換行符並丟棄 while(cin.getline(tem,60)) { //... } //空行終止 while(cin.getline(tem,60)&&tem[0]!='\0') { //... }
I/O異常
exceps()返回一個為字段,包含三位:eofbit,failbit,badbit。修改流狀態后,clear()函數將當前流狀態與exceps()的返回值比較,如果返回值中某一位被設置,當前流轉態位對應位也被設置,則clear()引發ios_base::failure異常,exceptions()返回good不會引發異常,ios_base::failure從exception類派生,因此包含一個what()函數。
#include <iostream> #include <exception> using namespace std; int main() { cin.exceptions(ios_base::failbit); int a=0,sum=0; try { while(cin>>a) sum+=a; } catch(ios_base::failure &bf) { cout<<bf.what()<<endl; } cout<<"last input:"<<a<<endl; cout<<"sum:"<<sum<<endl; return 0; }
#include <iostream> #include <exception> #include <fstream> using namespace std; //filebuf in; //istream i(&in); int main() { int a=0,sum=0; while(cin>>a)//只有狀態流良好(所有為都被清楚)的情況下,while或if才返回true sum+=a; cout<<"last input:"<<a<<endl; cout<<"sum:"<<sum<<endl; if(cin.fail()&&!cin.eof()) { cin.clear(); while(!isspace(cin.get()))//get提供不跳過空白字符讀的功能 continue; } else { cout<<" i cannot go on"<<endl; exit(1); } cout<<"now input a new number:"; cin>>a; return 0; }
cin.get(char &):到達文件末尾(真正的文件末尾,或鍵盤仿真文件末尾---win下ctrl+z---UNIX下ctrl+d,都不會給參數賦值,返回eof
cin.get():返回int類型的值,后不能跟抽取操作符,如:cin.get().get()>>c;
基於文件
繼承關系
程序寫入文件
- 創建一個ofstream對象管理輸出流
- 將對象與特定的文件關聯
- 用cout的方式使用該對象
- 使用colse()顯示關閉文件的鏈接;(對象過期時(程序中止))文件的鏈接會自動關閉
#include <iostream> #include <fstream> using namespace std; int main() { ofstream of; of.open("1.txt"); //ofstream of("1.txt");//也可以用構造函數 of<<"ds"; of.close();//關閉文件將刷新緩沖區,確保文件被更新 return 0;
}
讀取文件
- 創建一個ifstream對象管理輸出流
- 將對象與特定的文件關聯
- 用cin的方式使用該對象
- 使用colse()顯示關閉文件的鏈接;(對象過期時(程序中止))文件的鏈接會自動關閉
#include <iostream> #include <fstream> using namespace std; int main() { ifstream fin; fin.open("1.txt"); //ifstream fin("1.txt");//也可以用構造函數,可以用is_open()檢查是否打開成功,若成功流狀態為0 string s; fin>>s; cout<<s<<endl; fin.close();//只是斷開文件的鏈接,對象與他的緩沖區仍然存在 return 0; }
當文件流作為類成員時,其初始化只能是初始化列表方式,即構造對象時文件,不能在構造函數體中進行操作,否則文件打開失敗。
文本文件與二進制文件
文本文件:
所有內容都儲存為文本,如:以文本格式存儲123456,將存儲6個字符,計算機內部將浮點數轉為字符格式,這是<<插入操作符的工作;寫文本文件時,linux,UNIX。dos c++自動將換行符轉為回車和換行;讀文本文件時,將本地的換行符轉為c++格式;macintosh c++將換行轉為回車。
二進制文件:
計算機不存儲字符,而存儲64位double;字符的二進制和文本表示一樣(字符的ASCII二進制表示);較為精確,二進制保存數據更快,不需要轉換,占用空間小,可大量存儲;但二進制對於換行符,有歧義。二進制與文本文件的末未檢測方式也不同。
二進制和文本模式的區別:
在windows系統中,文本模式下,文件以"\r\n"代表換行。若以文本模式打開文件,並用fputs等函數寫入換行符"\n"時,函數會自動在"\n"前面加上"\r"。即實際寫入文件的是"\r\n" 。
在類Unix/Linux系統中文本模式下,文件以"\n"代表換行。所以Linux系統中在文本模式和二進制模式下並無區別
#include <iostream> #include <fstream> using namespace std; struct Stu { int age; double weight; }stu; int main() { ofstream of; of.open("1.txt"); //到達stu的結構地址,並且將6個字節復制到與of相關的文件中,用write函數可以以二進制形式存儲數據 of.write((char *)&stu,sizeof(stu)); of.close();//關閉文件將刷新緩沖區,確保文件被更新 //將文件中的sizeof(stu)個字節復制到結構體中 ifstream fin("1.txt"); fin.read((char *)&stu,sizeof(stu)); return 0; }
基於字符串的I/O
繼承關系
#include <iostream> #include <sstream> using namespace std; int main() { ostringstream os; string s("123"); os<<s; cout<<os.str()<<endl; ostringstream _os; _os<<os.str(); cout<<_os.str()<<endl;//讓_os從os中提取數據,str()成員函數的使用可以讓istringstream對象返回一個string字符串 return 0; }
清空stringstream對象中的內容
vector<string> s(v.size(),"");//確定字符串數組的大小 stringstream ss; for(int i=0;i<v.size();++i) { ss<<v[i]; s[i]=ss.str(); ss.str(""); //清空stringstream對象中的內容,clear()只是清空了流的狀態 }