C 清空輸入緩衝區,以及fflush(stdin)的使用誤區和解決方法
對C 語言初學者來說,fflush(stdin)函式被解釋為會清空輸入緩衝區的一個系統函式,這是一個曾經幾乎對過一半的說法,隨著電腦科學的進步,在學習的過程中的逐步完善,將fflush(stdin)函式的過去與現在分析一下。
Personal thinking:
fflush(stdin) 會清空輸入緩衝區中的內容,讀取時輸入緩衝區中的內容會被scanf函式逐個取走,正常case下scanf()函式可以根據返回值判斷成功取走的數目;當發生異常讀取的時候,如應該讀取一個整形,結果輸入緩衝區內當前的內容是個字串,發生讀取異常。發生讀取異常之後,輸入緩衝區中的內容並未被取走,那麼下次迴圈之時,scanf()函式發現輸入緩衝區中有內容(顯然編譯器不會關心這個內容是不是合法),於是不再等待user輸入,直接嘗試讀取輸入緩衝區中的內容,顯而易見的又是一次讀取異常,如此反覆。
include <stdio.h>
int main( void )
{
int val,ret;
while(fflush(stdin),(ret = scanf("%d",&val)) != EOF)
printf(“%d\n”, val);
return 0;
}
按照直覺,這個程式使用了fflush(stdin)以確保讀取異常時不干涉下一次的讀取。那麼問題來了,事實果真如此麼?
fflush(stdin)的前世今生
由Microsoft官方提供的MSDN 文件裡也清楚地寫著:fflush on input stream is an extension to the C standard(fflush 操作輸入流是對 C 標準的擴充,注意啊,是Extension!)。即C 標準中根本沒有定義 fflush(stdin),最新的C 11直接刪去了曾經打擦邊球的fflush(stdin)。
這也就是開頭說的fflush(stdin)曾經幾乎對過一半的原因:fflush()確實也是擴充套件,這就是幾乎對過的意思,在vs 2013之前的版本里,包括 vc++ 6.0 fflush(stdin)也確實管用。現在即使在vs 2015環境下fflush(stdin)也不再起作用,遑論lunix系統下呢,這就是曾經對一半。
當然,如果毫不在乎程式的移植性,在可用 fflush(stdin)的版本里面這麼寫也沒什麼大問題。
清空輸入緩衝區的可替代方法
既然fflush(stdin)現在在vs 2015 下不幹活了,那麼總得有接替背鍋的角色,實現清空緩衝區的角色,下面根據查閱的結果,給出兩種在C 可以實現清空輸入緩衝區功能的可行方案。
首先宣告下,使用setbuf(stdin,NULL)是GCC下可用的一種方法,但是沒有解決掉快取的問題,然而這裡不予深究。
在vs 2015 下,可以用下面兩種方法代替fflush(stdin)實現功能:
1)使用函式rewind(stdin)
從函式名上來看,這個函式應該是重定義了輸入緩衝區的Location or Size,用這個方法得到新緩衝區,前後兩個輸入緩衝區並不是一樣的(純猜測,輕點打臉!),其實這個函式好像也是非標準定義的(不是很確定,因為在 C 11與 C 99 的 更新裡面真沒看到這個,不過也可以作為暫時管用的半個。都說到這裡,多說一句,C 11 標準確實地刪除了gets()函式,用gets_s()代替,關於這一點放在最後一個連結內)
2)使用scanf(“%*[^\n]%*c”),原理是用掃描集將緩衝區中的字元全部讀取來實現清除輸入緩衝區的動作,就效果來說非常管用,而且還跨平臺。
乍一看這東西還有點深奧,給出詳細的解釋,也可以參考連結裡面的解釋說明。
對scanf(“%*[^\n]%*c”)解釋:
%〔^\n〕將逐個讀取緩衝區中的’\n’字元之前的其它字元,%後面的表示將讀取的這些字元丟棄,前遇到’\n’字元時便停止讀取操作,此時,緩衝區中尚有一個’\n’字元遺留,所以後面的%*c將讀取並丟棄這個遺留的換行符,這裡的星號和前面的星號作用相同。由於所有從鍵盤的輸入都是以回車結束的,而回車會產生一個’\n’字元,所以將’\n’連同它之前的字元全部讀取並丟棄之後,也就相當於清除了輸入緩衝區。
關於fflush(stdout)
如果不考慮fflush(stdin)這個坑的話,它的兄弟fflush(stdout)還是有很大的作用的,簡言之,fflush(stdout)強制輸出當前輸出緩衝區中的內容,一些在Debug下一些莫名其妙的error可以用fflush(stdout)立即輸出在處理過程中的中間結果來確定error所在。
在查閱過程中發現一句話:
fflush(stdin)對輸入流的操作是未定義的,所以這個還是要慎用,或許有副作用。
fflush(stdout)只是將需要輸出的輸出緩衝區中內容當即輸出,利於除錯且沒有什麼不良後果。
不管怎麼說,向當年的二極體一樣,fflush(stdin)也在計算機的發展過程中廣泛使用過,時過境遷。一回首已百年身,也許以後的學習過程中再也不會遇見fflush(stdin)了。
參考資料:
1.ISO/IEC 9899:1999 (E) Programming languages— C 7.19.5.2 The fflush function
2.The C Programming Language 2nd Edition By Kernighan & Ritchie