主题:[讨论]标准流猜想
ccpp
[专家分:9360] 发布于 2005-08-27 10:40:00
网上的cFAQ总说使用fflush(stdin)要谨慎,或者说不应使用。我个人认为只要了解了stdin流的原理,在使用起来应该没有问题。在整理了这些资料的过程中,我发现其中很多理论问题并没有想象的那样难于理解,只是那些“高人”在表述的过程中隐藏了很多的理论细节。
先说两大流:标准库的流分为两类:文本流(或称为字符流)和二进制流。
正文流把文件看作行的序列,每行包含0 个或多个字符,一行的最后有换行符号'\n'。正文流适合一般输出和输入,包括与人有关的输入输出。
二进制流用于把内存数据按内部形式直接存储入文件。二进制流操作保证,在写入文件后再以同样方式读回,信息的形式和内容都不改变。二进制流主要用于程序内部数据的保存和重新装入使用,其操作过程中不做信息转换,在保存或装入大批数据时有速度优势,但这种保存形式不适合人阅读。
进入正题,
int main()
{
char c1, c2;
scanf("%c", &c1);
scanf("%c", &c2);
printf("c1 is %c, c2 is %c", c2\1, c2);
return 0;
}
运行该程序, 输入AB后回车, 那么输出结果为: c1 is A, c2 is B。
如果输入一个字符A后回车 (要完成输入必须回车), 而后就如上直接结束了输出结
果为: c1 is A, c2 is ,变量c2输出的是1个换行符(因为字符A后的回车符赋给了c2)。
要解决以上问题, 可以在输入函数前加入清除函数fflush()修改以上程序变成:
int main()
{
char c1, c2;
scanf("%c", &c1);
fflush(stdin);/*也可以写成getchar();来达到同样的作用*/
scanf("%c", &c2);
printf("c1 is %c, c2 is %c", c1, c2);
return 0;
}
运行程序,如果输入一个字符A后回车, 会等待输入下一个字符。fflush(stdin);的作用是清除掉字符A后回车。若使用 getchar();也能读掉字符A后回车,达到同样的效果!
下面介绍一下标准流:
在程序启动时,会预先定义三个文本流(建立三个文件指针并指定值,但它们不需要显式地打开): 标准输入流(指针名为stdin)、标准输出流(stdout)和标准错误流(stderr)。stdin 通常与操作系统的标准输入连接,stdout 与操作系统的标准输出连接,stderr 通常直接与显示器连接,这说明stderr 不能重新定向(用于输出诊断信息)。在打开时,标准错误流不会完全缓冲;当且仅当流与交互的设备无联系时,标准输入和标准输出流才被完全缓冲(一种常见的方式,与输入设备联系时,比如需要键盘输入数据时,不等输出缓冲区写满就会刷新并显示在屏幕上,此时就不是完全缓冲)。
要把外存文件作为输入输出对象,一个可能方式是通过标准输入输出的重新定向,把标准流转接到指定文件。这样做能解决一些问题。但这种做法有很大局限性,因为这样形成的定向在程序执行期间不能改变。为能在程序中方便地根据需要使用各种文件,就必须利用标准库的文件操作函数(如fopen),通过为有关文件建立特定输入输出流的方式使用它们。
下面我们只研究stdin:
1 我们敲击键盘得到的是扫描码(扫描码有两个字节,低位字节是ASCⅡ码,高位字节是键位码),而不是ASCⅡ码。按键的扫描码会进入键盘的缓冲区。若程序有标准输入函数,接着键入 Enter后,连带着回车符一块把按键数据(扫描码转换ASCⅡ码形成文本流)送到输入缓冲区(stdin),标准输入函数接收数据后,会把未处理的数据留在输入缓冲区(stdin),这样的后果是随后的输入函数继续读入输入缓冲区中残留的数据。
如果残留在入缓冲区(stdin)中的数据内容无用,如上面例子的回车符 就需要要处理掉。可用 getchar()循环处理,或直接用fflush(stdin)清空缓冲区。
结论:在这个过程中必须注意,当程序执行到标准输入函数时,键盘的缓冲区中回车键的扫描码之前的其它键扫描码 ,才会被转换成文本流进入 标准输入流。
2 而非标准输入函数getche() ,getch()还有bioskey(0).这样的函数在使用时是不需要按“回车”键,也就是说他们直接读取键盘缓冲区的扫描码,这他们的特别之处。
如果在使用getch()之前,键盘缓冲区有不需要的扫描码,也得需要清除!下面是实现这个功能的函数
void Clear_Key_Buffer(void)//清除键盘缓冲区函数
{
int offset;
offset=peek(0x40,0x1a);
pokeb(0x40,0x1c,offset);
}
3 测试函数,其中注释掉的函数可取出来研究以上的理论。
程序运行后,快速连续输入: 1234回车5
结果确是:c1= 1, c2= 2, c3= 5, c4= 3
#include<stdio.h>
#include<conio.h>
#include<dos.h>
void Delay(int clicks)/*延迟函数*/
{
unsigned int far *clock=(unsigned int far *)0x0000046CL;
unsigned int now;
now=*clock;
while(abs(*clock-now)<clicks){}
}
void Clear_Key_Buffer(void)
{
int offset;
offset=peek(0x40,0x1a);
pokeb(0x40,0x1c,offset);
}
int main(void)
{
char c1,c2,c3,c4;
int i;
for(i=0;i<5;i++)
Delay(20);
/* Clear_Key_Buffer(); */
c1 = getche();
c2 = getchar(); /*只接收输入缓冲区*/
/* fflush(stdin); */
c3 = getche(); /*只接收键盘缓冲区*/
c4 = getchar();
printf("\nc1 = %c, c2 = %c,c3 = %c,c4 = %c", c1, c2,c3,c4);
getch();
return 0;
}
回复列表 (共29个回复)
11 楼
cmaster [专家分:9520] 发布于 2005-08-12 23:51:00
如果上面的还不算我没有话说了,如果算,
3。我是学C的,我用过TC,TC++,C-free,VC,BCB,用得最多的是TC,BCB,运行环境DOS 6.2,WINDOWS 98/me/2003 。其它系统没涉及过。我写过的代码不多,但累计至少也得以数万计。从未碰到使用fflush(stdin);出错的现象。当然,我的接触面是小了一点,代码也写得少,我也同样知道“网上的cFAQ总说使用fflush(stdin)要谨慎,或者说不应使用。”,但不管是自己还是网上我从未见过使用fflush(stdin);引起程序就崩溃的实例。
至少这里就没有UNIX系统,本质的差别。标准是那样就是那样。 我给你的哪个就是标准!!!
fflush(stdin)要谨慎,或者说不应使用
//为什么不应该,你知道吗,我上面有解释,如果你只是要你认为运行正确的,那么很多未定义的都可以用
不好意思,如果我的话让你不喜欢,但是事实就是事实。, 我要是自己看到的才会说的,否则我肯定说不保证,可能, 记得好象之类的词
我也很差的,我就懂点C
12 楼
knocker [专家分:3380] 发布于 2005-08-12 23:59:00
我建议你再好好读读楼主的文章,搞清什么是stdin!它不是键盘缓冲区!
13 楼
knocker [专家分:3380] 发布于 2005-08-13 00:05:00
还有,不是所有的fflush()都是一样的^_^,你想过没有?说到最后,你还是拿不出实例。我只想看实例。88了。
14 楼
eastcowboy [专家分:25370] 发布于 2005-08-13 10:26:00
说来惭愧,K&R那本书本来是我们学C的教材,可惜的是后面部分都没怎么看……现在放假在家书又不在手边。
我也认为未定义行为是危险的,虽然一个好的操作系统应该尽量避免崩溃,但是我们程序员也应该负点责吧?
两位似乎争论的过火了一点,不过就你们说的内容来看,我更愿意相信cmaster的。
15 楼
ccpp [专家分:9360] 发布于 2005-08-13 13:12:00
楼上的几位争论得比较激烈,不必如此。
我是菜鸟,我的想法在一个菜鸟的认识范围内,写出来更希望别人来指点,自己才能进步。我不是行家,我是整理了网上的材料才写出来的,所以引用他人写的内容。
但几个结论是自己的想法:
[color=FF0000]1. 标准输入流(stdin)和键盘缓冲区(OS-level input buffers)关系;[/color]
2. 标准输入流和键盘缓冲区分别对应着不同的输入函数,如scanf(),getchar()对应前者,getch(),getche()对应这后者。
3.当程序执行到标准输入函数时,键盘的缓冲区中回车键的扫描码之前的其它键扫 描码才会被转换ASC Ⅱ码送到输入缓冲区(stdin)。
又收集了一点资料补充一下前面的内容:
1)完全缓冲方式。对于输入而言,在填充时用来自文件的信息填满缓冲区,直至缓冲区信息用完再做下一次填充。对输出而言,直到缓冲区装满才执行一次实际向文件的输出操作。
2)行式缓冲(所谓的不完全缓冲)。对于输入,在填充时一次装入一行字符;输出时,一旦缓冲区里有了一个完整的行就送入文件.
对于标准输入函数如scanf(),getchar()的处理就是行式缓冲。这也是为什么在第一次输入时(输入缓冲区没有数据时),输入数据后我们要在键盘上按一下Enter键*,因为回车键就是换行符,表示一行的结尾。
int c;//不能用 char c,因为EOF 等于 -1
while((c=getchar()) != EOF)
{...}
结束标准输入流,在UIIX下按Ctrl+b, 在DOS下按Ctrl+z ,才能结束上面的循环。
但在DOS下按Ctrl+z后,还要补按一下 Enter。
Ctrl+b和Ctrl+z是在键盘上模拟文本文件结束。在DOS下补按一下 Enter,是因为DOS模拟的不完全,还得靠 Enter来完成行式缓冲。
*注:getch(),getche()就不需要回车键。
我是自学C,没有老师指点,自己一个人走了很多弯路,几乎放弃了。但自从上C论坛,和大家一起交流,学习的兴趣越来越高。论坛上的朋友完全是无私的帮助他人学习,这一点让我很感动
16 楼
ccpp [专家分:9360] 发布于 2005-08-13 13:21:00
我的最终结论是 fflush(stdin);处理键盘输入的不确定性,
正是它不能处理键盘缓冲区,而产生的误用(就是概念不清)。
17 楼
格子裙 [专家分:15760] 发布于 2005-08-13 16:03:00
fflush(stdin)要谨慎。
………………………………………………………………
的确这样。
不相信的人去 csdn.net上面的社区发个贴说上面的那句是错的。。。
你是被高手骂我我改姓。
18 楼
knocker [专家分:3380] 发布于 2005-08-13 17:01:00
fflush(stdin)要谨慎或者不能用。
这句话我不知听了多少次。Brian W.Kernighan & Dennis M.Ritchie也说,大牛也说,小菜也说,实际编程中我也很谨慎,但是,既然这个问题已经分析得如此头头是道,为什么持反对意见没有一个人写一个实例?说实话,我是从未碰到过。
总不能仅仅因为会被CSDN高手骂而弃用fflush(stdin)吧???
已有的事实已经证明fflush(stdin)是可用的或者说在某种环境某种条件下是可行。这个不需要我多说了吧?那么,在什么情况下什么环境什么条件下fflush(stdin)会引起出错?这个是不是我们要讨论的主题?你持反对意见的是不是应该给个实例?否则,我们讨论什么?人云亦云不是我的作风。
19 楼
zhangmou [专家分:14200] 发布于 2005-08-13 18:27:00
是啊。到真正要用的时候还不是要用~!
20 楼
格子裙 [专家分:15760] 发布于 2005-08-13 18:33:00
呵呵。10楼兄,你说了N次拜拜。可是再没走啊。。。看来你对你的意见蛮坚定的。。
你不是要实例吗?你去CSDN他们一定给你实例。。。
我来回复