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);’,把输入缓冲清空掉不就行了?”然而这是错的!CC++标准里从来没有定义过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)也没什么大问题。以下是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 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 更是会产生非常“有趣”的结果。有兴趣的朋友可以试一下。

关于fflush、缓冲区、scanf、EOF等问题真麻烦的更多相关文章

  1. oracle日期函数转换真麻烦。。。

    --Oracle trunc()函数的用法/**************日期********************/1.select trunc(sysdate) from dual  --2011 ...

  2. PAT 甲级 1060 Are They Equal (25 分)(科学计数法,接连做了2天,考虑要全面,坑点多,真麻烦)

    1060 Are They Equal (25 分)   If a machine can save only 3 significant digits, the float numbers 1230 ...

  3. 发现了合自己胃口的公众号,但文章太多翻来翻去真麻烦,还好我学了 Python

    现在我们大多数人都会或多或少的关注几个公众号,如果发现一个比较合自己胃口的号 对公众号中的文章一定是每篇必读的. 有时候我们关注到宝藏型公众号时发现其历史文章已经好几百甚至上千篇了,而作者又只对其中自 ...

  4. 关于scanf、getchar、getch、getche缓冲区分析——C语言

    缓冲区 根据数据刷新的时机可以将缓冲区的类型分为:全缓冲.行缓冲.无缓冲 (注意:Windows下的输出设备没有缓冲区,意思是printf是无缓冲的,但是在Linux下printf就是行缓冲的,至于为 ...

  5. scanf类型不匹配造成死循环

        int i = 0; while (flag) { printf("please input a number >>> "); scanf("% ...

  6. Linux C 字符串输入函数 gets()、fgets()、scanf() 详解

    一.gets() 函数详解 gets()函数用来从 标准输入设备(键盘)读取字符串直到 回车结束,但回车符('\n')不属于这个字符串. 调用格式为: gets(str); 其中str为字符串变量(字 ...

  7. C语言scanf函数详细解释

    原文链接 函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准 ...

  8. scanf 用法及陷阱(转)

    函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设备( ...

  9. C语言scanf函数详解

    函数名: scanf  功 能: 运行格式化输入  用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设 ...

随机推荐

  1. phpstorm 右下角显示updating indices,一直有任务卡着

    其实就是生成的这个node_modules目录内文件太多了,选中node_modules这个目录右键,选择Excluded 一直在加载忽略掉这个文件就可以了

  2. Python 的selenium使用

    一.Radio 判断radio是否已经被选中 driver.find_element_by_id().is_selected() 其返回值为bool类型,True or False 二.frame f ...

  3. 详解如何设置CentOS 7开机自动获取IP地址

    本例中以CentOS 7举例说明如何设置Linux开机自动获取IP地址和设置固定IP地址. 自动获取动态IP地址 1.输入“ip addr”并按回车键确定,发现无法获取IP(CentOS 7默认没有i ...

  4. 为Spring Cloud Config Server配置远程git仓库

    简介 虽然在开发过程,在本地创建git仓库操作起来非常方便,但是在实际项目应用中,多个项目组需要通过一个中心服务器来共享配置,所以Spring Cloud配置中心支持远程git仓库,以使分散的项目组更 ...

  5. poj3308 Paratroopers 最大流 最小点权覆盖

    题意:有一个n*m的矩阵,告诉了在每一行或者每一列安装大炮的代价,每一个大炮可以瞬间消灭这一行或者这一列的所有敌人,然后告诉了敌人可能出现的L个坐标位置,问如何安置大炮,使花费最小.如果一个敌人位于第 ...

  6. Post with HttpClient4

    转载:http://www.cnblogs.com/luxiaoxun/p/6165237.html 作者:阿凡卢 出处:http://www.cnblogs.com/luxiaoxun/ HttpC ...

  7. configParse模块

    一.配置文件简介 在各种程序里面都有配置文件,为了对配置文件进行操作. python中引入了configParse模块进行操作. 配置数值类型: 配置文件中,我们看到的bool型,整数型,在我们操作的 ...

  8. 【zznu-2060】 Minsum Plus(最小正子段和)

    题目描述 题意简单到令人发指! 序列A由N个整数组成,从中选出一个连续的子序列,使得这个子序列的和为正数,且和为所有和大于零的子序列中的最小值. 将这个值输出,若无解,输出no solution. 输 ...

  9. String.Remove

    String.Remove方法注意事项: 1.该方法不改变元字符串: 2.str  = ‘’: str.Remove(str.Length-3);明显超限,但是不报错,返回值为''; str = '1 ...

  10. LeetCode OJ:Swap Nodes in Pairs(成对交换节点)

    Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...