关于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 更是会产生很是“有趣”的结果。有兴趣的朋友能够试一下。