續:C++ Iostreams 用法詳解(一)整體把握
首先說我們最常用的兩個全局對象cin和cout,以下摘自MSDN:
or to read the . The rules for doing so are outlined in the description of the class Class. You can also insert values to or to write the . The rules for doing so are outlined in the description of the class Class.
可以看出,其中cin是istream的對象,可以從中提取數據,而cout是ostream的對象,可以向中輸出數據。
什麼是和呢?可能對於我們這些90後來說不是那麼容易理解的,畢竟這是字符界面的概念,而我們在開始接觸計算機的時候就已經是普遍的windows圖形界面的操作系統了。但是windows操作系統還是為我們提供了一個模擬dos這種字符界面的程序cmd,讓我們大致對這個有一定的了解。而在Linux系統中我們更容易說清楚這個概念:在執行一個shell命令時,會默認打開三個標准文件,即標准輸入文件(stdin),通常對應終端的鍵盤;標准輸出文件(stdout)和標准錯誤輸出文件(stderr),這兩個文件都對應終端的屏幕。進程將從標准輸入文件中得到輸入數據,將正常輸出數據輸出到標准輸出文件,而將錯誤信息送到標准錯誤文件中。
這樣說比較了解了吧,因為我們在windows下面寫一個win32 控制台應用程序的時候,其實我們的這個程序就是類似於Linux中執行在shell中的命令,所以這個程序在執行的時候當然可以帶有參數(就是main函數的參數int argc和char* argv[]了),而且有標准輸入緩沖區和標准輸出緩沖區的概念了。只不過我們在windows下剛開始學習編程語言的時候,總是會用一些圖形界面的IDE(例如VC++6.0就是我的啟蒙IDE),所以對這個程序的運行機制並不是很清楚,只知道摁一下運行按鍵就自動完成了編譯、連接、打開一個cmd並直接運行我們寫的這個程序了。
有了標准輸入和輸出的概念(最好把他們像Linux裡面一樣看成是設備文件),接下來就要說一下這個cin和這個cout到底是怎麼完成輸入輸出機制的了。首先來看一下下面這個例子:
#include <iostream>#include <string>using namespace std;int main(){int j = 0;while(++j>0) for(int i=0;i>0;i++);string str;cin >> str;cout << str << endl;system("pause");}
用嵌套的循環來做了一個延時,當我們的程序(win32 控制台應用程序)在正常運行的時候,我們在鍵盤上敲下五個字符"hello",這個時候屏幕上並不會顯示任何東西,因為雖然我們的輸入都保存到了輸入緩沖區中,但是我們並沒有把它顯示到屏幕上來。當我們第一次調用cin>>str的時候,這時其實是調用了cin這個istream對象的>>運算符的方法,它首先讓我們的進程從運行(running)狀態轉換到等待I/O(waiting)狀態,然後將輸入緩沖區中的內容都輸出到屏幕上來,這個時候我們剛才在鍵盤上按的hello就都顯示出來了,然後當我們繼續在鍵盤上按五個字符"world"的時候,輸入緩沖區繼續吸收我們輸入的字符,然後被cin對象顯示到屏幕上來,直到我們在鍵盤上按回車鍵(正常情況下)時,這時輸入緩沖區也同時吸收了一個換行字符'\n',這個時候輸入緩沖區中就有11個字符"helloworld\n"了,然後cin>>str開始將輸入緩沖區中的這11個字符全部抽取(extract)出來存到對象cin的streambuf中(後面會講到,這其實就是存儲的buffer,而cin對象擁有的其實是streambuf對象的指針)。到這一步,還沒有跟我們的str對象有任何關系,cin對象只是從標准輸入輸出中提取出了數據而已(這時我們可以理解為輸入緩沖區中已經空了)。
之後的工作才關系到str對象,cin對象會根據str的類型(string)去格式化stream中的內容,即將前10個字符"helloworld"存到str對象中去,同時從streambuf中將這10個字符和結束字符'\n'清除(這其實是通過移動get指針來實現的,後面會說到),然後這條語句cin<<str;終於執行結束了。如果我們的代碼改成
這時格式化工作就變為:前10個字符"helloworld"存到str對象中去,並將str[10]改為0作為字符串結束標志,前10個字符"helloworld"存到str對象中去,同時從streambuf中將這10個字符和結束字符'\n' “清除”。 這裡需要說明的是,對於每次我們使用cin>>時,首先cin判斷自己的streambuf是否為空,如果是空的話,就觸發一次進程狀態的轉換,然後開始等待用戶的輸入等過程。但是如果streambuf不是空的,則會先嘗試從streambuf中的數據中去格式化所需的數據。當然,這個格式化過程並不一定都是正確的,這個問題後面再論。 我們在使用cin來從標准輸入緩沖區中得到字符串時,有時候使用cin >> str並不能解決問題,因為對於這樣的操作,在最後解析streambuf中的數據的時候,它會以空格' '、制表符'\t'、換行符'\n'來作為格式化字符串的結束字符,也就是說比如上面的代碼當我們輸入"hello world\n"的時候,我們得到的字符串為"hello",因為在遇到空格的時候就已經結束了字符串,注意這時streambuf中還有剩下的數據"world\n",如果我們現在再次執行cin >> str則會以'\n'為結束字符,獲取到字符串"world"。那如果我想一次性的獲取字符串"hello world"要怎麼做呢? istream也提供了兩個獲取整行字符串的(其實就是以換行符'\n'作為格式化字符串的結束字符)成員函數,分別就是get和getline。關於這個兩個函數的使用以及其各種重載的版本等等我在這裡就不多說了,自己去查MSDN吧。我在這裡只說一下這兩個函數最大的區別,那就是get函數在從streanbuf中格式化數據的時候,雖然也是以'\n'作為結束符,但是並不會清除掉streambuf中的這個字符,也就是說當我輸入字符串"hello world\n"的時候,get函數會正確的得到字符串"hello world",但是會將字符'\n'留在streambuf中,而getline函數將得到與get函數一樣的結果,但是不會將字符'\n'留在streambuf中。這有什麼區別呢?區別就比較大了,考慮下面的代碼:char str[20];cin >> str;
現在我輸入字符串"hello world\n"('\n'就是回車鍵),成功的得到了字符串str1內容為"hello world",這時streambuf中還剩下'\n',繼續進行cin.get(str2,20),由於streambuf是非空的,就會直接格式化出str2,於是我們就得到str2的內容為"",空的!但是如果我們上面使用的是getline函數,就會正常的讓我們輸入兩個字符串了。 還有,istream還提供了成員函數ignore,可以使get指針跳過字符,具體去查MSDN吧。如果在上面的代碼中兩個get函數之間插入cin.ignore()代碼也會正常的讓我們輸入兩個字符串。 對於 cout標准輸出過程,就跟上面的過程有點類似,可以從上面的例子中看到cin過程和標准輸出過程是完全互不相關的。每次使用cout進行輸出時,都會自動的調用flush(將streambuf中的數據顯示到屏幕上),所以在這裡了解flush並沒有太大的意義,後面的文件操作部分也會講到。而關於數據的格式化輸出,在後面也會講到,所以現在也就不說了。cout用起來還是比較簡單的。 關於istream和ostream的更多成員函數大家可以去查MSDN了解。 沒什麼寫博客的經驗,所以可能寫的比較啰嗦(我自己也感覺很啰嗦)。另外再強調我還是新手,有大神發現我有說錯的請一定提出啊。歡迎交流,一起學習。char str1[20],str2[20];cin.get(str1,20);cin.get(str2,20);