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 更是会产生非常“有趣”的结果。有兴趣的朋友可以试一下。

转载于:https://www.cnblogs.com/xuangong/p/3162736.html

关于fflush、缓冲区、scanf、EOF等问题真麻烦相关推荐

  1. ~scanf()和scanf()!=EOF

    两者作用是相同的. ~是按位取反 scanf的返回值是输入值的个数 如果没有输入值就是返回-1 -1按位取反结果是0 while(~scanf("%d", &n))就是当没 ...

  2. C中scanf()!EOF的理解

    EOF是文件结尾标志,EOF是一个计算机术语,为End Of File的缩写,在操作系统中表示资料源无更多的资料可读取.资料源通常称为档案或串流.通常在文本的最后存在此字符表示资料结束.而在文本文件中 ...

  3. 备案域名绑定服务器后 提示需要备案_这事真麻烦!备案网站迁移的要注意,不然网站会被判定为空壳网站...

    浅谈:备案域名与服务器的联系,空壳网站的形成以及解决办法 案例: 之前在阿里云买的服务器,网站域名也在阿里云备的案,现在阿里云服务器快到期了,并且由于某些原因需要将网站迁移到西部数码,问:之前域名的备 ...

  4. 这事真麻烦!备案网站迁移的要注意,不然网站会被判定为空壳网站

    浅谈:备案域名与服务器的联系,空壳网站的形成以及解决办法 案例: 之前在阿里云买的服务器,网站域名也在阿里云备的案,现在阿里云服务器快到期了,并且由于某些原因需要将网站迁移到西部数码,问:之前域名的备 ...

  5. 今天修改了个仿新浪爱问的知识人系统,真累,部署真麻烦。

    网站的名字叫:我问你答网 网址是:http://www5tiwen.com 希望能很好的被百度和GOOGle收录.不然就没有意思了!

  6. C语言scanf缓冲区去掉多余回车键

    #include<stdio.h>int main(){int i;scanf("%d",&i);printf("i=%d\n",i);ch ...

  7. %d 跟%i 的区别以及scanf和sscanf的用法

    scanf 用%i能够获得8/16进制的值, 比如0011就是9,0x11就是17,当然如果读取到08或者09就会出错了. scanf简介 scanf函数,与printf函数一样,都被定义在stdio ...

  8. fflush(stdin)和fflush(stdout)和rewind(stdin)的区别和作用

    理解:fflush(stdin)和fflush(stdout)和rewind(stdin) 小结论(多谢网友zhao4zhong1为我解答疑难): 1.在每个最后不带\n的printf后面加fflus ...

  9. C语言基础之--scanf函数

    写代码的时候,我们可以看得出,几乎每一个C程序都会包括输入输出.输入输出是程序中最基本的操作之一.C语言本身并不提供输入输出的语句,输入输出操作是由C标准函数库中的函数来实现的.今天就先说说输入:sc ...

最新文章

  1. LLVM与Clang编译图例
  2. ORA-01747: user.table.column, table.column 或列说明无效 异常解决方法总结
  3. 开发日记-20190903 关键词 C程序gcc编译过程
  4. 【SAP业务模式】之ICS(五):定价配置
  5. Replicate(网络复制),ActorRole(角色),Ownership(所有权)以及RPC(远程调用)等等...
  6. 小甲鱼python视频解读_小甲鱼python视频弟十二讲(关于字符串的方法及注释下)...
  7. linux ifconfig找不到
  8. layui的表格可以动态添加行吗_答疑分享052:插入表格,数据分析更方便
  9. 30秒实现Vue吸顶效果
  10. LeetCode 1325. 删除给定值的叶子节点(递归)
  11. 33.JAVA编程思想——JAVA IO File类
  12. 《新财富》2015年第13届分析师排名
  13. 【渝粤教育】国家开放大学2019年春季 0390-22T古代诗歌散文专题 参考试题
  14. 常用Docker 镜像命令(二)
  15. javascript 对后台返回的数据进行分类
  16. 34款管理系统、ERP、CRM、OA等(冠唐\金蝶等)
  17. 室内设计数据手册pdf_室内设计资料集pdf下载-室内设计资料集电子版pdf高清扫描版-东坡下载...
  18. 华为荣耀V9手机通过在Fastboot模式写ramdisk.img来获取ROOT权限 | 华为荣耀V9怎么获取ROOT权限 | 华为荣耀V9怎么用面具Magisk做ROOT权限
  19. php怎么添加extension,php中没有extension怎么办_后端开发
  20. 开心农场前端程序源码

热门文章

  1. muduo之ThreadPool
  2. mbstowcs 和 wcstombs函数:C语言提供的宽字符和多字节字符转换函数
  3. centos下载速度很慢解决办法
  4. jsp里面不能使用${pageContext.request.contextPath}解决方案
  5. spring boot一:入门
  6. 让数字营销向人工智能借一双慧眼
  7. ZOJ 1696 Viva Confetti 计算几何
  8. Mysql忘记用户密码的解决办法
  9. ZH奶酪:Ionic通过angularJS+tabs-item-hide实现自定义隐藏tab
  10. html表格自动换行