(轉,精彩!清空緩存的方法)C/C++ 誤區二:fflush(stdin)


2008-5-7 12:02:00
1.       為什么 fflush(stdin) 是錯的

首先請看以下程序:

#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);,把輸入緩沖清空掉不就行了?”然而這是錯的!CC++標准里從來沒有定義過 fflush(stdin)。也許有人會說:“可是我用 fflush(stdin) 解決了這個問題,你怎么能說是錯的呢?”的確,某些編譯器(如VC6)支持用 fflush(stdin) 來清空輸入緩沖,但是並非所有編譯器都要支持這個功能(linux 下的 gcc 不支持),因為標准中根本沒有定義 fflush(stdin)MSDN 文檔里也清楚地寫着fflush on input stream is an extension to the C standardfflush 操作輸入流是對 C 標准的擴充)。當然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也沒什么大問題。以下是 C99 fflush 函數的定義:

 

int fflush(FILE *stream);

 

如果 stream 指向輸出流或者更新流update stream),並且這個更新流
最近執行的操作不是輸入,那么 fflush 函數將把這個流中任何待寫數據傳送至
宿主環境(host environment)寫入文件。否則,它的行為是未定義的。

原文如下:


int 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)  是不正確的,至少是移植性不好的。

 

 

  1. 2.       清空輸入緩沖區的方法

 

        雖然不可以用 fflush(stdin),但是我們可以自己寫代碼來清空輸入緩沖區。只需要在 scanf 函數后面加上幾句簡單的代碼就可以了。

        /* C 版本 */
        #include <stdio.h> 


        int main( void )
        {
            int i, c;
            
for ( ; ; )
            {
                fputs("Please input an integer: ", stdout);
                scanf("%d", &i);

                if ( feof(stdin) || ferror(stdin) )
                {
/* 如果用戶輸入文件結束標志(或文件已被讀完), */
                  /* 或者發生讀寫錯誤,則退出循環               */

           
                    /* do something */
                    break;
                }
               
/* 沒有發生錯誤,清空輸入流。                 */
                /* 通過 while 循環把輸入流中的余留數據“吃”掉 */
                while ( (c = getchar()) != '\n' && c != EOF ) ;
                /* 使用 scanf("%*[^\n]");(詳見下面備注①) 也可以清空輸入流, */

                /* 不過會殘留 \n 字符。                          */

               printf("%d\n", i);
            }

             return 0;
        }


       
/* C++ 版本 */
        #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;
        }

參考資料

ISO/IEC 9899:1999 (E) Programming languages C 7.19.5.2 The fflush function

The C Programming Language 2nd Edition By Kernighan & Ritchie

ISO/IEC 14882(1998-9-01)Programming languages C++

****************************************
備注①:

   星號(*)表示讀指定類型的數據但不保存
 
   [ ]  掃描字符集合——正則表達示,字符簇:當在一組方括號里使用^時,它表示“非”或“排除”的意思。
   [^\n] 定義一個集合, 即除'\n'外的任意字符

   詳注: ANSI C 標准向 scanf() 增加了一種新特性,稱為掃描集(scanset)。 掃描集定義一個字符集合,可由 scanf() 讀入其中允許的字符並賦給對應字符數組。 掃描集合由一對方括號中的一串字符定義,左方括號前必須綴以百分號。 例如,以下的掃描集使 scanf() 讀入字符 A、B 和 C:
    %[ABC]
   使用掃描集時,scanf() 連續吃進集合中的字符並放入對應的字符數組,直到發現不在集合中的字符為止(即掃描集僅讀匹配的字符,一旦發現不匹配,馬上返回)。返回時,數組中放置以 null 結尾、由讀入字符組成的字符串。
    char nums[20]="";
    scanf("%[0-9]",nums);
    用字符 ^ 可以說明補集。把 ^ 字符放為掃描集的第一字符時,構成其它字符組成的命令的補集合,指示 scanf() 只接受未說明的其它字符。
    其他的詳見《正則表達式》。


 
 



一個例子
xiaou發表評論於2007-7-16 14:09:40
#include <iostream>
#include <stdexcept>//標准庫異常類
#include <limits> // 為了使用numeric_limits
using namespace std;
int main()
{
    int a,b;
    while(1){
        cin>>a>>b;
        try{
            if(cin.good()) cout<<a+b<<endl;
            else throw runtime_error("輸入有誤,重新輸入?y OR n :");
        }
        catch(runtime_error err){
            cout<<err.what();
            char ch;
            cin.clear();
            cin.ignore( numeric_limits<streamsize>::max(), '\n' );
            cin>>ch;
            if(cin.bad()||ch!='y') break;
        }
    }//whlie()
    system("pause");
}

 
 

 

另一個例子:C和C++雜合的清空緩存方法(薦)
小鼬(游客)發表評論於2008-5-7 12:20:26

#include<iostream>
using namespace std;

template <class T>
class input{
      T t;
public:
      input(char *s,T min,T max){
          cout<<s<<endl;
          while(1){
              if(cin>>t)
                  if(t<=max && t>=min)
                      break;
              //C和C++雜合的:
              cin.clear();
      int c;
       while( (c=getchar())!='\n'&&c!=EOF );

          }
      }
};

int main()
{
    input<int> o("test",-1,10);
    system("pause");
}

 

C++的更好的方法:
2009-5-19 22:16:34
1、使用cin.clear()清除錯誤狀態
2、用cin.sync() 清空輸入緩存區


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM