C++ 流的操作 | 初识IO类、文件流、string流的使用
文章目錄
- 前言
- IO頭文件
- iostream
- fstream
- sstream
- 流的使用
- 不能拷貝或對 IO對象 賦值
- 條件狀態與 iostate 類型
- 輸出緩沖區
- 文件流
- fstream類型
- 文件模式
- string流
- istringstream
- ostringstream
前言
我們在使用 C++ 的過程中,總避免不了 IO操作,比如經常用到的一些 IO庫設施:
- istream:(輸入流)類型,提供輸入操作。
- ostream:(輸出流)類型,提供輸出操作。
- cin:一個 istream 對象,從標準輸入讀取數據。
- cout:一個 ostream 對象,向標準輸出寫入數據。
- cerr:一個 ostream 對象,通常用于輸出程序錯誤消息,寫入到標準錯誤。
- >>運算符:用來從一個 istream 對象讀取輸入數據。
- <<運算符:用來向一個 ostream 對象寫入輸出數據。
- getline函數:從一個給定的 istream 讀取一行數據,存入一個給定的 string 對象中。
但實際上可能僅僅是懵懵懂懂在使用,如果不深入了解的話,實際上這樣的使用是淺薄的。
IO頭文件
iostream
定義了用于讀寫流的基本類型。
- istream,wistream 從流讀取數據
- ostream,wostream 向流寫入數據
- iostream,wiostream 讀寫流
fstream
定義了讀寫命名文件的類型。
- ifstream,wifstream 從文件讀取數據
- ofstream,wofstream 向文件寫入數據
- fstream,wfstream 讀寫文件
sstream
定義了讀寫內存string對象的類型。
- istringstream,wistringstream 從 string 讀取數據
- ostringstream,wostringstream 向 string 寫入數據
- stringstream,wstringstream 讀寫 string
流的使用
標準庫通過繼承使我們忽略不同類型流之間的差異。舉例來說,類型 ifstream 和 istringstream 都繼承自 istream。因此,我們如何使用 cin ,就可以同樣地使用這些類型的對象。
不能拷貝或對 IO對象 賦值
ofstream out1, out2; out1 = out2; // error:不能對流對象賦值 ofstream printf(ofstream); // error: 不能初始化ofstream參數 out2 = printf(out2); // error: 不能拷貝流對象- 由于不能拷貝IO對象,因此我們也不能將形參或返回類型設置為流類型。
- 進行IO操作的函數通常以引用方式傳遞和返回流。讀寫一個IO對象會改變其狀態,因此傳遞和返回的引用不能是const的。
條件狀態與 iostate 類型
IO操作使用不當的話會發生錯誤,而如果是發生在系統深處的錯誤,那么就超出了應用程序可以修正的范圍。但也有一些錯誤是可以恢復的,IO類也提供了一些函數和標志來訪問、操縱流的條件狀態。
下面對表中的四個條件位作進一步介紹。
iostate 類型
IO庫定義了一個與機器無關的 iostate 類型,它提供了表達流狀態功能。
IO庫定義了 4個 iostate類型 的 constexpr 值(常量表達式),表示特定的位模式:
- badbit: 表示系統級錯誤,如不可恢復的讀寫錯誤。通常情況下,一旦 badbit 被置位,流就無法再使用了。
- failbit: 在發生可恢復錯誤后被置位,如期望讀取數值卻讀出一個字符等錯誤。這種問題通常是可以修正的,流還可以繼續使用。
- eofbit: 如果到達文件結束位置,連同 failbit 一起被置位。
- goodbit: 值為 0 時,表示流未發生錯誤。
對他們進行一個簡單的使用:
auto old_state = cin.rdstate(); // 返回流cin的當前狀態,返回值類型為 strm::iostate cin.clear(); // 將cin所有條件位復位,換言之,使cin有效 // clear重載版本允許有參數,接受一個iostate值,表示流的新狀態 cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); // 先用rdstate讀出當前條件狀態,再將failbit和badbit復位生成新狀態。 process_input(cin); // 使用cin cin.setstate(old_state); // 將cin置為原有狀態輸出緩沖區
以前對于輸出緩沖區是沒什么概念的……直到在做美團往年筆試題的時候,有道編程題如果用 endl 作為換行會報超時,原因是 endl 頻繁刷新輸出緩沖區,因此需要用 '\n' 。
操作系統的 IO操作 是很耗時的,緩沖機制使操作系統將程序的多個輸出操作組合成單一的系統級寫操作(寫到顯示設備上),對性能的提升是巨大的。
導致緩沖刷新(數據真正寫到輸出設備或文件)的原因有很多:
關于第三點,共有三種操作符可用來刷新緩沖:
- endl: 換行并刷新緩沖區
- flush: 僅刷新緩沖區,但不輸出任何額外字符
- ends: 向緩沖區插入一個空字符然后刷新緩沖區
關于第四點:
- unitbuf操縱符: 每次寫操作之后都進行一次 flush 操作
- nounitbuf操縱符: 重置流,使其恢復默認的緩沖區刷新機制
值得一提的是,如果程序崩潰,輸出緩沖區不會被刷新。
關于第五點,C++提供了 tie函數 來查看當前對象關聯的輸入輸出流,tie 有兩個重載版本:
- 無參數版本: 返回指向輸出流的指針。當前對象若關聯了一個輸出流,則返回指向該流的指針;若未關聯流,則返回空指針。
- 參數為一個指向 ostream 的指針: 將當前對象關聯到此 ostream 。
每個流同時最多關聯到一個流,但多個流可以同時關聯到同一個 ostream 。
示例:
cin.tie(&cout); // 標準庫默認將 cin 和 cout 關聯在一起 cin.tie() = cin.tie(nullptr); // 通過傳遞空指針,讓 cin 不再與其他流關聯文件流
fstream類型
除了繼承自 iostream類型 的行為外,fstream類型 還增加了一些新的成員來管理與流關聯的文件。
open函數
fstrm(s) 之所以能在調用時打開文件s,是因為自動調用了 open函數 ,等價于:
ifstream in; // 輸入文件流未與任何文件關聯 in.open(ifile); // 打開指定文件,并與in綁定對一個已經打開的文件流調用 open 會失敗,此時 failbit 會被置位,隨后使用文件流的操作都會失敗。因此,調用 open 后檢測是否成功是個好習慣:
if(in) // 成功 else // 不成功如果想要將文件流關聯到另一個文件,必須先關閉已關聯的文件:
in.cloes(); in.open(ifile);open 成功調用會將 good() 設為 true。
close函數
當一個 fstream對象 被銷毀時,close 會自動被調用。
文件模式
每個文件流類型都定義了一個默認的文件模式,當我們未指定文件模式時,就使用此默認模式。
- 與 ifstream關聯 的文件默認以 in模式 打開;
- 與 ofstream關聯 的文件默認以 out模式 打開;
- 與 fstream關聯 的文件默認以 in和out模式 打開。
雖然不論是調用 open 打開文件,還是 fstrm(s) 這樣隱式打開文件,都可以指定文件模式,但指定文件模式有如下限制:
- 為了保留以 out 模式打開的文件的內容,我們必須同時指定 app 模式,這樣只會將數據追加寫到文件末尾;
- 或者同時指定 in 模式,即打開文件同時進行讀寫操作。
關于第五點,舉例詳細講一下:
/*截斷*/ ofstream out1("file1"); // 隱含以out模式打開文件并截斷文件 ofstream out2("file1", ofstream::out); // 隱含地截斷文件 ofstream out3("file1", ofstream::out | ofstream::trunc); // 顯式實現out模式打開文件并截斷/*app模式保留文件內容*/ ofstream app1("file2", ofstream:app); // 隱含out模式 ofstream app2("file2", ofstream:out | ofstream:app);string流
同樣的,除了繼承自 iostream 的操作,sstream 也增加了獨有的操作。
istringstream
我們經常會碰到處理整行字符串的問題,比如:比較版本號
用雙指針截取字符串當然是一種方法,但是使用 istringstream 這個標準庫提供的利器會更加方便。當然,兩種方法的時間、空間復雜度是一樣的。
下面通過分析 istringstream 的使用來進一步理解如何用:
class Solution { public:int compareVersion(string version1, string version2) {istringstream in1(version1); // 將文本version1與輸入流in1綁定istringstream in2(version2);int a, b;char c;while(in1.good() || in2.good()){in1 >> a; // 從in1中讀取int數據到a中,遇到空白符or非int數據停下in2 >> b;if(a > b) return 1;if(a < b) return -1;a = b = 0;in1 >> c; // 從in1中讀取char類型數據到c中,遇到空白符or非char數據停下in2 >> c;}return 0;} };再比如,有這樣的輸入,人名和他們的常用密碼,一個人可能有多種常用密碼:
cmy 12345 22345 lx 6644 lhy 6633 1221 5665那么我們可以這樣處理:
struct per_pw{string name;vector<string> pw; } string s, word; // s暫存來自輸入的一行文本 vector<per_pw> people; while(getline(cin, s)){ // 處理一行文本,也就是一個人的信息per_pw pp;istringstream in(s); // 將in綁定到剛讀取的sin >> pp.name; // 讀取名字while(in >> word) // 讀取密碼pp.pw.push_back(word); // 密碼存入pp的pw數組中people.push_back(pp); // 將這個人的信息保存在people數組中 }ostringstream
當我們希望將多個輸出最后一起打印時,ostringstream 是很有用的。舉個簡單的例子:
ostringstream out; // 創建一個未綁定的輸出流 vector<string> vs = {"cmy", "lx", "lhy"}; for (string s : vs) {out << s << " "; } cout << out.str() << endl; // str():返回out保存的string的拷貝,也就是將out轉換為string類型。我們使用標準的輸出運算符<<向 out 寫入數據,有趣的是,這些寫入操作實際上轉換為 string 操作,向 out 中的 string 對象添加字符。
總結
以上是生活随笔為你收集整理的C++ 流的操作 | 初识IO类、文件流、string流的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 经济滞涨的可怕之处 经济滞涨的后果
- 下一篇: C++继承详解