关于fflush、缓冲区、scanf、EOF等问题真麻烦
1.為什么?fflush(stdin)是錯的
http://u637.springnote.com/pages/6288463.xhtml(已經bad request了)
首先請看以下程序:
#include?<stdio.h>
int main( void )
{
int i;
for (;;) {
fputs("Please input an integer: ", stdout);
scanf("%d", &i);
printf("%d\n", i);
}
return 0;
}
?
這個程序首先會提示用戶輸入一個整數,然后等待用戶輸入,如果用戶輸入的是整數,程序會輸出剛才輸入的整數,并且再次提示用戶輸入一個整數,然后等待用戶輸入。但是一旦用戶輸入的不是整數(如小數或者字母),假設scanf函數最后一次得到的整數是2,那么程序會不停地輸出“Please input an integer: 2”。這是因為scanf("%d", &i);只能接受整數,如果用戶輸入了字母,則這個字母會遺留在“輸入緩沖區”中。因為緩沖中有數據,故而scanf函數不會等待用戶輸入,直接就去緩沖中讀取,可是緩沖中的卻是字母,這個字母再次被遺留在緩沖中,如此反復,從而導致不停地輸出“Please input an integer: 2”。
?
也許有人會說:“居然這樣,那么在scanf函數后面加上‘fflush(stdin);’,把輸入緩沖清空掉不就行了?”然而這是錯的!C和C++的標準里從來沒有定義過fflush(stdin)。也許有人會說:“可是我用fflush(stdin)解決了這個問題,你怎么能說是錯的呢?”的確,某些編譯器(如VC6)支持用fflush(stdin)來清空輸入緩沖,但是并非所有編譯器都要支持這個功能(linux 下的?gcc就不支持),因為標準中根本沒有定義fflush(stdin)。MSDN文檔里也清楚地寫著fflush on input stream is anextension?to the C standard(fflush操作輸入流是對C標準的擴充)。當然,如果你毫不在乎程序的移植性,用fflush(stdin)也沒什么大問題。以下是C99對fflush函數的定義:
?
int fflush(FILE?*stream);
?
如果?stream指向輸出流或者更新流(update stream),并且這個更新流
最近執行的操作不是輸入,那么 fflush 函數將把這個流中任何待寫數據傳送至
宿主環境(host environment)寫入文件。否則,它的行為是未定義的。
原文如下:
fflush(FILE?*stream);
If stream points to an output stream or an update stream in which
the most recentoperation was not input, the fflush function causes
any unwritten data for thatstream to be delivered to the host environment
to be written to the file;otherwise, the behavior is undefined.
?
其中,宿主環境可以理解為操作系統或內核等。
??? 由此可知,如果stream指向輸入流(如stdin),那么fflush函數的行為是不確定的。故而使用fflush(stdin) ?是不正確的,至少是移植性不好的。
?
?
2.清空輸入緩沖區的方法
?雖然不可以用fflush(stdin),但是我們可以自己寫代碼來清空輸入緩沖區。只需要在scanf函數后面加上幾句簡單的代碼就可以了。
??????? #include?<stdio.h>
??????? int main( void )
??????? {
??????????? int i, c;
?for ( ; ; )
??????????? {
??????? ??????? fputs("Please input an integer: ", stdout);
??????????????? scanf("%d", &i);
?????????????if ( feof(stdin) || ferror(stdin) )
??????????????? {
??????? ??????????? break;
????????????????}
??????? ??????? while ( (c = getchar()) != '\n' && c != EOF ) ;
???????????????printf("%d\n", i);
??????????? }
????????return 0;
??????? }
??????? #include?<iostream>
??????? #include?<limits>?// 為了使用numeric_limits
???? using std::cout;
??????? using std::endl;
????????using std::cin;?
??????? using std::numeric_limits;
??????? using std::streamsize;
???? int main()
??????? {
??????????? int value;?
??????? ??? for ( ; ; )
??????? ??? {
??????????????? cout << "Enter an integer: ";
??????? ??????? cin >> value;
??????????????? if ( cin.eof() || cin.bad() )
??????? ??????? {?// 如果用戶輸入文件結束標志(或文件已被讀完),
??????????????? ? // 或者發生讀寫錯誤,則退出循環
???? ??????????? // do something
??????????????????? break;
????????????? ??}
??????? ????????// 讀到非法字符后,輸入流將處于出錯狀態,
??????????????? // 為了繼續獲取輸入,首先要調用 clear 函數
??????? ??????? // 來清除輸入流的錯誤標記,然后才能調用
??????????????? // ignore 函數來清除輸入流中的數據。
??????? ??????? cin.clear();
????????????????// numeric_limits<streamsize>::max() 返回輸入緩沖的大小。
??????? ??????? // ignore 函數在此將把輸入流中的數據清空。
??????????????? // 這兩個函數的具體用法請讀者自行查詢。
??????? ??????? cin.ignore( numeric_limits<streamsize>::max(), '\n' );
??????????????? cout << value << '\n';
??????????? }
???? ??? return 0;
??????? }
?
?
這是我在別的論壇看到的!樓主文章的觀點不對!誤導人!!
1.? ?? ? 為什么 fflush(stdin) 是錯的
-----------------------------------------
C和C++的標準里從來沒有定義過 fflush(stdin)。
---------------------------------------------
錯誤,不能說fflush(stdin)是錯的。作者列出了標準的內容,這顯示作者的確有看過標準,但對標準的內容理解錯誤。標準指出fflush用于輸入流的結果是未定義的,但是未定義并不等于是錯誤!同時c和c++的標準也并非從來沒有定義過fflush(stdin),恰恰相反,標準說fflush用于輸入流的結果是未定義的本身就是對fflush(stdin)的定義!就是對fflush(stdin)提出的規定!只不過,其結果是未定義而已!
結論應該是:使用fflush(stdin)會產生移植性問題,是不良風格代碼,但不是錯誤。
作者所提出的解決方案:
if ( scanf("%d", &i) != EOF ) {?
? ?? ?? ?? ?while ( (c=getchar()) != '\n' && c != EOF ) {
? ?? ?? ?? ?? ?? ?;
? ?? ?? ?? ?}
}
并沒有完全解決了問題,存在重大的漏洞。主要問題在于,使用getchar()這種方法并沒有清除EOF標志。如果用tc2.0、tc2.01、tc3.0、tc3.1等等編譯器運行上述代碼,輸入時用ctrl+z結尾或者直接輸入ctrl+z,程序肯定會進入一個死循環!
原因就是getchar()方式并沒有清除EOF標志,我在這里所說的EOF標志并非指函數返回的EOF,而是指當I/O函數遇到EOF時在其內部產生的EOF標志。
偶推薦用rewind(stdin)這個方法,rewind不僅清除了stdin中的內容,還清除EOF標志,用下列語句:
scanf("%d", &i);
rewind(stdin);
代替上述if語句,無論你如何輸入ctrl+z,都不會進入死循環,同時也簡單得多,是比較完美的解決方法。
?
?
?
首先感謝您的評論,它促使我重新審視了我這篇文章,并且修正了文中的一些錯漏。特別是文中的兩個程序,如果 stdin 被重定向到文件時,會出現死循環。現在我已經把這個問題修正了,就算 stdin 被重定向到文件,也不會出現死循環。如果本文還有其它不足之處,敬請指出,我將不吝感激!
然后,對樓上的一些觀點不敢茍同,在此發表一些淺見。
1. 按照樓上對錯誤的定義,我說 fflush(stdin) 是錯的的確是錯了。不過,每個人對錯誤的理解都不一樣。我認為,如果某種功能明明可以用標準代碼實現,而放著不用,或者不會用,卻依賴編譯器/系統特定的功能實現,這就是錯誤。當然,這只是我的看法。還有,我覺得使用編譯器/系統特定的功能(如 fflush(stdin);)不算不良風格代碼。我認為不良風格是指代碼一大堆一大堆地堆放在一起,沒有認真地縮進,也缺乏注釋,代碼層次不清晰明了,功能模塊分工不細,等等。另外,對樓上“標準說fflush用于輸入流的結果是未定義的本身就是對fflush(stdin)的定義”這個見解非常欽佩。我覺得這個見解別樹一格,非常獨到,新穎。樓上的腦筋真靈!我就從來沒想過這點,慚愧!
2. 我的方案的確存在問題,謝謝你的指出。但問題并不是你所說的那樣,而是出在重定向上。如果 stdin 被重定向到文件,我原來的程序的確會導致死循環。
? ? 樓上說“輸入時用ctrl+z結尾或者直接輸入ctrl+z,程序肯定會進入一個死循環!”,我用 TC 測試過了,直接輸入 ctrl+z 不會死循環,但是輸入一些數據后用 ctrl+z 結尾的確會出現死循環。不過這個卻是 TC 的問題!請看以下代碼:
? ?? ???#include <stdio.h>
? ?? ???int main( void )
? ?? ???{
? ?? ?? ?? ?int ch;
? ? ? ? ? ? while ( getchar() != EOF ) ;
??? ? ? ?? ?if ( feof(stdin) )
? ? ? ?? ???{? ?
? ? ? ? ? ? ? ? printf("Oh, No! EOF indicator is set now!\n");
? ? ? ?? ???}
? ? ? ?? ???clearerr(stdin);
? ? ? ?? ???if ( !feof(stdin) )
? ? ? ?? ???{? ?
? ? ? ? ? ? ? ? printf("Ok! EOF indicator is unset now!\n");
? ? ? ?? ???}
? ? ? ?? ???if ( getchar() == EOF )
? ? ? ?? ???{? ?
? ? ? ? ? ? ? ? printf("But why still cannot read from stdin?\n");
? ? ? ?? ???}? ? ? ??
? ?? ?? ?? ?return 0;
? ?? ???}
用 TC 編譯運行時輸入 21312313^Z,得到結果如下:
? ?? ???21312313^Z
? ?? ???Oh, No! EOF indicator is set now!
? ?? ???Ok! EOF indicator is unset now!
? ?? ???But why still cannot read from stdin?
由此可見,就算沒有標注 EOF 標記,如果輸入時以 ^Z 結尾,也會導致不能從 stdin 中讀取數據!這是 TC 的問題!我原來的程序之所以會在輸入以 ^Z 結尾時會出現死循環,就是因為不能從 stdin 中讀取數據!至于樓上用了 rewind(stdin); 之后就能從 stdin 中讀取數據,看來是 TC 特定的功能!
? ? 不過也要感謝樓上,我因此才發現如果 stdin 被重定向到文件,我的程序會出現死循環。不過當初我寫那兩個程序也僅僅是為了演示一下如何清空 stdin,并沒有考慮太多其它因素。
3. 對于樓上提出的方案表示強烈反對!樓上提出的方案比使用 fflush(stdin); 高明不到哪里去,都是使用了編譯器特定的功能。
? ? 首先我們看一下標準對 rewind 函數的定義:
? ?? ???void rewind(FILE *stream);
? ? ? ?? ???rewind 函數把 stream 指向的流的文件位置標記設置為文件
? ?? ???開始。如果不考慮它還會清除流的錯誤標記,則 rewind 函數
? ?? ???等同于
? ? ? ?? ?? ?? ?(void)fseek(stream, 0L, SEEK_SET);
? ? ? ??
? ? ? ? 原文如下:
? ?? ?? ?? ?The rewind function sets the file position indicator for
? ?? ???the stream pointed to by stream to the beginning of the
? ?? ???file. It is equivalent to
? ? ? ?? ?? ?? ?(void)fseek(stream, 0L, SEEK_SET)
? ? ? ? except that the error indicator for the stream is also
? ?? ???cleared.
? ? K&R 的 The C Programming Language, Second Edition 干脆就說
? ?? ???rewind(fp); 等同于 fseek(fp, 0L, SEEK_SET); clearerr(fp);
? ? 由此可見,標準只是說 rewind 可以把流的文件位置標記設置為文件開始,并且清除流的錯誤標記,卻沒有定義 rewind(stdin) 可以清空 stdin 的內容,所以使用 rewind(stdin) 不一定能清空 stdin。而且,如果 stdin 被重定向到文件的話,使用 rewind 更是會產生非常“有趣”的結果。有興趣的朋友可以試一下。
轉載于:https://www.cnblogs.com/xuangong/p/3162736.html
總結
以上是生活随笔為你收集整理的关于fflush、缓冲区、scanf、EOF等问题真麻烦的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: devi into python 笔记(
- 下一篇: 直方图与匹配