都說細節決定成敗,我覺得的編程來說,特別是面試的時候細節最能決定的是關鍵時候你能裝的程度,所以我想有個系列記錄我遇到的各種我遇到的細節問題,以備不時之需啊。
cout<<"Hello,world"<<endl;作為我真正學習寫程序的起點我一直對其懷有感激之 心,想到大一學C++的時候看到這個的時候覺得,這就是寫程序嗎?這就是我以前夢想的能夠讓電腦聽我指揮,什么軟件,游戲,病毒的開發嗎?不像啊,這個也 沒什么作用啊,就看着個黑屏幕顯示一句話,什么也做不了,那時也不懂得什么東西的學習都是漫長的過程,但是好在后面也沒想太多,相比當時大多數興趣被這黑 屏澆滅而后再也沒有研究過編程的同學,我還算幸運的莫名的堅持了下來。這幾天,無意中看到一個C++教材第一頁的hello world,腦子里面竟然想到當時我初學的時候老師跟我們說想把東西輸出到屏幕上還可以用cout,cerr和clog,我還記得那個時候我還都試了試, 發現都可以,但是也沒有太在意其中的學問,只是疑問一樣的功能為什么要搞三個名字,老師自然也是搬ppt的東西,也沒有解釋。后來我學會了查msdn,查 到了這三個的區別在於重定向和緩沖,直到前幾天,我居然在項目中遇到了有關這三個的問題,雖然不難解決,於是我覺得我還是有必要研究研究這個細節。
一切都先從msdn開始比較靠譜,對於這三個的差異,msdn是這么解釋的:
The ostream class, through the derived class basic_ostream, supports the predefined stream objects:
-
cout standard output
-
cerr standard error with limited buffering
-
clog similar to cerr but with full buffering
這里面關鍵的一個詞是buffering(什么?重定向,這里暫時沒有看到,生命有限,一次不能 研究的太多),buffer存在於計算機軟硬件的各個方面,緩沖區的意義很好理解,最大的好處無非是可以提高效率,每天跑到銀行存1000塊錢和你十天湊 齊10000塊錢一次性向銀行存入,所耗費的時間果斷不能是一樣的,所以無論在哪個方面緩沖都是人類的好朋友啊。這三個的差別從字面上看,第一個根本沒有 提buffering什么事,第二個有limited buffering,第三個是full buffering,有什么區別呢,先從第一個開始吧,繼續在msdn中查找cout,看看這家伙怎么說的。讓人是失望的是這里面基本沒什么有用的信息, 但是都說超鏈接改變世界,這里面的example有個cerr的超連接,正好它也排在第二個,順手看看cerr好了,里面是這樣描述的:
The object controls unbuffered insertions to the standard error output as a byte stream. Once the object is constructed, the expression cerr.flags & unitbuf is nonzero.
對比cout和前面的內容,unbuffered這個關鍵詞再一次引起我們的關注,另外后面還多了一句話,提到了flags和unitbuf,在看了 clog發現和cout沒什么差別之后,每一個發育自然並且想研究一下的人都會查看一下unitbuf或者flags。查看flags發現這是一個和 fmtflags相關的函數,而且后面還有一個例子:
#include <iostream> #include <fstream> int main ( ) { using namespace std; cout << cout.flags( ) << endl; cout.flags( ios::dec | ios::boolalpha ); cout << cout.flags( ); }
運行一下會查看到cout的flag值,會看到輸出是兩個很奇怪的整數531和16896,這個時候就要去看看fmtflags到底是什么,msdn上是這么寫的:
Constants to specify the appearance of output.
static const fmtflags boolalpha(15), dec(10), fixed(14), hex(12), internal(9), left(7), oct(11), right(8), scientific(13), showbase(4), showpoint(5), showpos(6), skipws(1), unitbuf(2), uppercase(3), adjustfield, basefield, floatfield; |
第一反應是這是什么玩意兒,還好msdn是高大全的參考,在Remark部分可以看到如下語句:
Supports the manipulators in ios.
The type is a bitmask type that describes an object that can store format flags.
兩層含義,第一個支持ios里面的操作符,第二個是這是一個bitmask類型,也就是說上面的18個fmtflags類型定義的玩意兒都是這個類型 的,ios里面的操作符很好理解,就是那些可以定義cout(當然同樣作為輸出流的cerr,clog也是可以被定義的)格式的東西,比如最簡單的 cout<<hex<<12;可以輸出十六進制的12,也就是說fmtflags這個的作用就是使用另一種方式設定格式,設定的 方法前面那個例子中也顯示了,雖然是定義了18個fmtflags變量,但是在這個詞條的底部也說明了最后三個是前面15個某些的組合,也就是說可以大膽 的猜想flags有15位,具體這15個具體有什么作用,對此有興趣的可以繼續探究,上面每個變量旁邊的括號中的數字是我親測得每個對應的flags的 位,假設最低位是從1開始的。
另外,上面的程序531轉換成二進制就是1000000001,也就是默認的cout設定的是dec和skipws兩個 位,skipws全名叫做skipwhitespace,這也很容易解釋了cout的時候輸出的十進制的數並且能夠忽略前導空格,這算一個細節吧。
在所有的flag里面,憑借一股風騷的意識你應該能發現有一個很眼熟啊--unitbuf,這個在前面cerr的說明里出現過,滾動一下鼠標可以發現 cerr的說明中說這個位在cerr里面是非0的,而其余兩個沒有特別說明,那么我們就看看初始情況下clog和cerr的flag是什么,可以發現 clog和cout是一樣的,cerr卻是1000000011,是不是有種看破的感覺,unitbuf既然有個buf,再加上cerr關於buffer 的特別說明,可以猜想這個位就是控制buf的,再看看它的說明:
unitbuf, to flush output after each insertion.
翻譯成中文就是每次輸出的時候刷新輸出,這句話在你什么也不知道的時候確實很疑惑,什么叫flush?前面說了這里面的符號位和ios的操作都是對應的, 那么看看ios里面有什么相關的內容好了,找到ios members可以看到其中有個函數也是較unitbuf,這個時候就得趕緊點進去看看,Remark部分是這么寫的:
Note that endl also flushes the buffer.
如果你從沒有接觸過這方面的內容,那么恭喜你,除了endl能換行之外,你至少知道了endl還可以刷新緩沖區,現在面臨一個新問題,什么叫flushes the buffer,msdn不好找了,這就要回到緩沖區這個概念上來了。
所謂緩沖區前面已經大概的解釋了,你每天能賺1000(貌似有點多),然后你去存銀行,這就是沒有緩沖,但是你嫌這樣太麻煩,你決定先 攢幾天錢再存銀行,這樣的話就是有緩存。但是總會有個條件讓你達到要去存這個行為,這個條件或者是到了多少天,或者是到了多少錢,或者是你老婆迫使你趕緊 去存,因為身上現金多了容易遭搶啊或者現金多了容易起干壞事,畢竟男人的銀行卡一般都不在自己身上。但是事務都有兩面性,雖然你每天存很耗時間,但是至少 保證比好幾天一存的安全性大(假設銀行是信得過的)。
輸出緩沖區和上面這個過程也很像,如果不是設置的強制刷新的這個位,那么緩沖區只能在兩種情況下刷新,第一個是緩沖區滿了(時間到了或者錢總量到了),或 者是強制要其刷新,比如使用了endl,flush等等(你老婆叫你去存錢),或者設置了unitbuf位。回到這三個輸出流上面來,cerr既然設置了 unitbuf項的位,它每次輸出都會刷新輸出緩沖區,這樣的好處就是在程序出了一些特殊錯誤的情況下仍然能夠有輸出(費時但是安全),比如遞歸堆棧崩 潰,而cout不會,那么刷新緩沖區是往哪兒刷新呢?在這里是往輸出設備(不一定是屏幕),通俗的說就是如果刷新沒有進行,那么就不會顯示在控制台的窗口 上,好了,費了這么多話,最關鍵的要眼見為實,怎么樣才能看到這一個現象呢?按照上面費的這么多話,cerr和cout在表現上應該是不同的,所以我們用 這兩個試試看。
想看到這個過程,我決定用一個Sleep,用來延緩看到這個過程,除了加上windows頭文件,在main里面寫如下代碼:
cout<< "A"; Sleep(1000); cout<<flush;
如果上面說的是真的,那么應該是過了1秒左右之后再顯示'A',可是你卻發現它直接出來了,沒有那關鍵的一秒延遲,再換乘cerr,發現現象是一樣 的,cerr應該是這樣的,我覺得輸出cout的flags看看,發現unitbuf沒有改變阿,是0啊,沒錯啊,我被這個問題困擾了很久,最后我終於找 到了原因,上面費的那么多話都是C++標准,而VS里面使用std里面的cout不一定是符合這個標准的,無論你用什么都會自動加一個flush,通俗的 說就是無論你用哪一個都會立即輸出,原因應該是無論發生錯誤都能輸出東西,以方便調試等等,好了,問題找到了,得找個解決方案了,最終我找到了一個c語言 中的函數,在這些代碼之前加上:
char* pbuffer = new char[512]; setbuf(stdout, pbuffer);
這個函數就是設置緩沖區(或者你用更新的setvbuf),用完這個之后,你會發現A是一秒之后再出現的,你可以試試刪除掉flush,你會發現'A'根 本不會出現,換成cerr不用cerr同樣就完全不會出現這個問題,'A'是立即出現的。這說明了緩沖和立即刷新之間的區別,而且還可以把第二個cout 換成cout<<flush<<'B',你會發現B根本不會出現,因為B之后沒有再刷新了,你還可以保證第一個cout不變,第 二個改成cerr,雖然刷新了緩沖區,但是仍然不能輸出,說明這三個輸出流用的不是一個緩沖區,這個是我下一篇的主題。
上面還說了,緩沖區滿了也會強制輸出,那么我們用以下代碼看看結果:
char* pbuffer = new char[1024]; setbuf(stdout, pbuffer); for(int i=0;i<1024;i++) cout<<'A'; t<<'B'; Sleep(1000);
可以看到1024個A輸出了,可是B沒有輸出,因為1024個A占滿了緩沖區,強制刷新了緩存,后面的B自然輸出不能。
最后,cout的c到底代表什么,這個也是在我學習c++很長一段時間后才知道的,很簡單我居然沒想到,c代表的是控制台的console。
【我還是兩邊一起更新吧】