关于fflush、缓冲区、scanf、EOF等问题真麻烦
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);’,把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过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)也没什么大问题。以下是C99对fflush函数的定义:
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等问题真麻烦的更多相关文章
- oracle日期函数转换真麻烦。。。
--Oracle trunc()函数的用法/**************日期********************/1.select trunc(sysdate) from dual --2011 ...
- 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 ...
- 发现了合自己胃口的公众号,但文章太多翻来翻去真麻烦,还好我学了 Python
现在我们大多数人都会或多或少的关注几个公众号,如果发现一个比较合自己胃口的号 对公众号中的文章一定是每篇必读的. 有时候我们关注到宝藏型公众号时发现其历史文章已经好几百甚至上千篇了,而作者又只对其中自 ...
- 关于scanf、getchar、getch、getche缓冲区分析——C语言
缓冲区 根据数据刷新的时机可以将缓冲区的类型分为:全缓冲.行缓冲.无缓冲 (注意:Windows下的输出设备没有缓冲区,意思是printf是无缓冲的,但是在Linux下printf就是行缓冲的,至于为 ...
- scanf类型不匹配造成死循环
int i = 0; while (flag) { printf("please input a number >>> "); scanf("% ...
- Linux C 字符串输入函数 gets()、fgets()、scanf() 详解
一.gets() 函数详解 gets()函数用来从 标准输入设备(键盘)读取字符串直到 回车结束,但回车符('\n')不属于这个字符串. 调用格式为: gets(str); 其中str为字符串变量(字 ...
- C语言scanf函数详细解释
原文链接 函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准 ...
- scanf 用法及陷阱(转)
函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设备( ...
- C语言scanf函数详解
函数名: scanf 功 能: 运行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设 ...
随机推荐
- Sql字符串操作函数
1.去空格函数 (1).LTRIM() 把字符串头部的空格去掉. (2).RTRIM() 把字符串尾部的空格去掉. 2.字符转换函数(1).ASCII()返回字符表达式最左端字符的ASCII 码值.在 ...
- [javascript]jQuery绑定事件方法:on()
语法: $(selector).on(event,childSelector,data,function) on(event,childSelector,data,function):在被选元素及子元 ...
- Lograge(2350✨) 在产品环境显示定制改良的日志输出。
Lograge https://github.com/roidrage/lograge 改良Rails默认的请求日志的记录. 它会精明的处理好那些noisy和无用的,未解析的,在context中运行多 ...
- HIVE学习(待更新)
1 安装hive 下载 http://mirrors.shu.edu.cn/apache/hive/hive-1.2.2/,红框中的不需要编译. 由于hive是默认将元数据保存在本地内嵌的 Derby ...
- Hibernate[延迟加载] [三种状态] [脏检查] [缓存机制]
一.持久化对象的唯一标识 java中按内存地址不同区分同一个类的不同对象,关系数据库用主键区分同一条记录,Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系 什么是OID? 解析: ...
- Maven 中的dependencies与dependencyManagement的区别
1.dependencyManagement 在Maven中dependencyManagement的作用其实相当于一个对所依赖jar包进行版本管理的管理器 在pom.xml文件中,jar的版本判断的 ...
- 【Python】高阶函数介绍
其实函数可以作为变量,之前学过C++,对于这种用法并不奇怪.无非就是函数充当变量,可以传入函数而已. 下面分别介绍 Python 中常见的高阶函数:map/reduce, filter, sorted ...
- 我要复习python啦(一)
一.变量 那些曾经怎么也看不懂的东西,突然有一天就懂了.这就是复习的力量吗? 1 变量的赋值 a = 10 做了上面的图所描述的事情 1)开辟一块内存,创建一个值为10的整数 2)创建一个a的标记 3 ...
- IGMP 因特网组管理协议
IGMP(Internet Group Management Protocol)作为因特网组管理协议,用于多播. 与ICMP一样, IGMP也被当作 IP 层的一部分,IGMP报文也通过IP数 ...
- APUE学习笔记——7main()函数启动与退出
程序的启动与退出过程 先上图,了解进程运行的机制. 内核首先调用exec,运行C启动进程,C启动进程会调用main()函数. 其他所有函数都是由main函数直接或间接调用的. ...