回 帖 发 新 帖 刷新版面

主题:[转帖]C/C++ 误区二:fflush(stdin)

C/C++ 误区二:fflush(stdin)
作者:antigloss


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);’,把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(gcc3.2不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standard(fflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 
fflush(stdin) 也没什么大问题。以下是 C99 对 fflush 函数的定义:

int fflush(FILE *stream);

如果stream指向输出流或者更新流(update stream),并且这个更新流
最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream
指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。
fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush
函数会给那些流打上错误标记,并且返回EOF,否则返回0。

由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用
fflush(stdin) 是不正确的,至少是移植性不好的。

 

2.       清空输入缓冲区的方法

 虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。

              /* C 版本 */

#include <stdio.h>

 

int main( void )

{

    int i, c;

    for (;;) {

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

        if ( scanf("%d", &i) != EOF ) { /* 如果用户输入的不是 EOF */

            /* while循环会把输入缓冲中的残留字符清空 */

            /* 读者可以根据需要把它改成宏或者内联函数 */

            /* 注:C99中也定义了内联函数,gcc3.2支持 */

            while ( (c=getchar()) != '\n' && c != EOF ) {

                  ;

            } /* end of while */
        }

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

    }

    return 0;

}

 

/* C++ 版本 */

#include <iostream>

#include <limits> // 为了使用numeric_limits

 

using std::cout;

using std::endl;

using std::cin;

 

int main( )

{

       int value; 

       for (;;) {

              cout << "Enter an integer: ";

              cin >> value;

             /* 读到非法字符后,输入流将处于出错状态,

               * 为了继续获取输入,首先要调用clear函数

               * 来清除输入流的错误标记,然后才能调用

               * ignore函数来清除输入缓冲区中的数据。 */           

              cin.clear( );

              /* numeric_limits<streamsize>::max( ) 返回缓冲区的大小。

               * ignore 函数在此将把输入缓冲区中的数据清空。

       * 这两个函数的具体用法请读者自行查询。 */

              cin.ignore( std::numeric_limits<std::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++

回复列表 (共13个回复)

沙发

长知识。

板凳

输入缓冲是用户的输入,用户输入都是宝贵的,你不应该不检查的丢弃.

3 楼

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,都不会进入死循环,同时也简单得多,是比较完美的解决方法。

4 楼

[quote]
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用fflush(stdin) 是不正确的,至少是移植性不好的。
[/quote]

作者以移植性为判断正确与错误的标准...

5 楼

To tell you the truth:

Please don't read some series such as "C/C++ 误区二" "C/C++ 误区三" etc

Those people think he/she knows a lot and start to write series to teach others, most of them (not all of them) are misleading!!!!

The samething happens in other Java forums too, someone almost know nothing about Java, started to write "classic 误区 in Java..."

I decided not to read those garbage, just in case I want to criticize again...

But I found C语言爱好者 started to comment, that made me come here...

Haha!

6 楼

I don't know why our moderators are so cheap, when they see something in 1,  2, 3 series, immediately added as "精华贴 ". Made garbage permanently misleading beginners....

Sigh!!!!!!

7 楼

mark

8 楼

作者以移植性为判断正确与错误的标准...
---------------------------------------
移植性 != 错误

9 楼

这是作者的判断标准,与我无关!

10 楼

偶并没有说跟谁有关,偶一直都在说“移植性 != 错误”

我来回复

您尚未登录,请登录后再回复。点此登录或注册