關於fflush、緩衝區、scanf、EOF等問題真麻煩

1.爲何 fflush(stdin)是錯的html

http://u637.springnote.com/pages/6288463.xhtml(已經bad request了)linux

首先請看如下程序:ios

#include <stdio.h>c++

int main( void )spring

{函數

int i;測試

for (;;) {ui

fputs("Please input an integer: ", stdout);spa

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);,把輸入緩衝清空掉不就好了?」然而這是錯的!CC++標準裏歷來沒有定義過fflush(stdin)。也許有人會說:「但是我用fflush(stdin)解決了這個問題,你怎麼能說是錯的呢?」的確,某些編譯器(如VC6)支持用fflush(stdin)來清空輸入緩衝,可是並不是全部編譯器都要支持這個功能(linux 下的 gcc不支持),由於標準中根本沒有定義fflush(stdin)MSDN文檔裏也清楚地寫着fflush on input stream is anextension to the C standardfflush操做輸入流是對C標準的擴充)。固然,若是你絕不在意程序的移植性,用fflush(stdin)也沒什麼大問題。如下是C99fflush函數的定義:

 

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 recent
operation was not input, the fflush function causes
any unwritten data for that
stream 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.0一、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 更是會產生很是「有趣」的結果。有興趣的朋友能夠試一下。

相關文章
相關標籤/搜索