回 帖 发 新 帖 刷新版面

主题:[讨论]标准流猜想

   网上的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个回复)

21 楼

20楼的兄弟:

请你仔细看看时间,说88是昨天晚上12点,我得睡觉不是么?

一个问题既然提出来了,不搞明白心里能舒坦么?你既然提到CSDN的“大牛”,虽然我平时不太上CSDN,但总可以找一下这类的贴子吧?用“fflush(stdin)”在CSDN总共找到有如下贴子:
    
√请问fflush(stdin)是什么意思(szws) 10 7 7-19 21:10 管理
http://community.csdn.net/Expert/topic/4152/4152587.xml?temp=.8236658

√fflush(stdin)为什么无效(milk_) 100 4 6-17 12:13 管理

http://community.csdn.net/Expert/topic/4088/4088875.xml?temp=.5088159

√到底什么时候需要使用fflush?怎么网上搜不到答案?(xiuxiuzi) 1 3 6-6 12:40 管理

http://community.csdn.net/Expert/topic/4061/4061403.xml?temp=.5223657

√编程高手指教:什么时候应该用到fflush()函数?(50分)(jJbeginner) 50 3 3-1 18:19 管理

http://community.csdn.net/Expert/topic/3816/3816034.xml?temp=.8041498

√fflush函数在DEV-C++和linux下的两种不同表现?(lswx) 20 10 12-15 08:50 管理

http://community.csdn.net/Expert/topic/3643/3643187.xml?temp=.177746

√fflush,free,getch,getchar(fire314159) 100 19 8-25 12:45 管理
http://community.csdn.net/Expert/topic/3306/3306544.xml?temp=.0333063



我看了一下,其中只有一贴有参考价值,其余的还没有cmaster说得清楚详细。

http://community.csdn.net/Expert/topic/3643/3643187.xml?temp=.177746

注意ww425的回答,我不熟悉unix系统,我只知道在win下的编译器TC,TC++,VC,C-free,BCB,我还没有碰到fflush(stdin)失效的实例。

  





22 楼

似乎从目前的资料来看fflush(stdin);与编译器相关,只有特定的编译器fflush(stdin)是不支持清输入流(如linux下的GCC?是这个吧?我没用过)。

23 楼

你看看我的那段很长的话,拜托,我都给你解释了为什么了,而且还告诉你K&R说了

我觉得我说服你了,你不承认罢了,你的知识还不够多,你总是说别人说了,K&R这么精确,而且我的理由都给了 代码的注释部分告诉了你为什么不能


c1 = _write(stream->_fd, (char *)stream->_buf, count);
//写入!!!!!!如果stream是stdin,你觉得write输入流是有什么行为??????????????????这是为什么所在


“”“还有,不是所有的fflush()都是一样的^_^,你想过没有?说到最后,你还是拿不出实例。”“”
//不是所有的fflush()都是一样的^_^,

看吧,看看什么是fflush,记住,参考K&R第二版 中文版 220 页:
原文如下:
int fflush(FILE*stream)
对输出流,fflush函数将已写到缓冲区但尚未写入文件的所有数据写到文件中。对输入流结果是未定义的。
//如果你觉得还能用你就用吧,是可以用的,世界上没有什么不可以的,我们不用再讨论了,如果你还要说。









24 楼

你的解释并不能成立。
a. K&R C并非标准C。
    ANSI C标准化工作的一个主要目标是清除原来K&R C语言中的不安全、不合理、不精确、不完善的东西。由此也产生了ANSI C与K&R C之间的差异。
b.你死抱一本《The C Programming Language》不放,死抱C标准不放,并没有考虑到不同编译器的fflush()完全有可能不同。
   C语言标准提供的是语言的基本定义,而各个C程序设计环境可能有许多自己的扩充。包括对标准库函数的改写。
   一个很明显的理由,只要对stream做一判断即可。这验证了为什么在绝大多数情况下fflush(stdin)不出错的原因。据说VC是支持fflush(stdin)的,不过我没看到相关文档。
c.你提供的fflush()源码很可能是MINIX中的fflush()源码。我现手头没《The C Programming Language》,不知道书中有没有说明其书中源码出自什么地方.

25 楼

看了了下面的贴子,确实有价值
  http://community.csdn.net/Expert/topic/3643/3643187.xml?temp=.177746

  看来标准C是一回事,而编译器的具体实现又是另一回事,
  好象scanf()处理浮点数据的数组一样,一些编译器会出现问题,而有一些则没有

26 楼

我手头那本
《The C Programming Language》
是建立在unix基础上的~!

27 楼

c1 = _write(stream->_fd, (char *)stream->_buf, count);
//写入!!!!!!如果stream是stdin,你觉得write输入流是有什么行为??????????????????这是为什么所在


再来看看,这个c1将会什么?如果stream是stdin,_write的行为发生错误,按常规它应返回EOF(没见_write源码只能这样按常理分析推断一下),但实际上没有,它返回是count。这个可以用:

    printf("%d",fflush(stdin));
来验证。

那么,只有一种情形,当赋给_write的参数是错误时并且_write函数又不具备容错能力即所谓鲁棒性不好,它才不按常理返回一个EOF或者其它,它返回的是count.我觉得这种可能性不大,不可想象一个库函数会这样写。

28 楼

a. K&R C并非标准C。
    ANSI C标准化工作的一个主要目标是清除原来K&R C语言中的不安全、不合理、不精确、不完善的东西。由此也产生了ANSI C与K&R C之间的差异。

//我服了! 第二版是针对新的标准的,懂吗?是标准之后写的,


b.你死抱一本《The C Programming Language》不放,死抱C标准不放,并没有考虑到不同编译器的fflush()完全有可能不同。

//依赖编译器,如果我做个编译器跟标准不一样,那?  但是你都说K&R的不是,那你这个好象也有道理


看来标准C是一回事,而编译器的具体实现又是另一回事,

//一个性质的说话


C语言标准提供的是语言的基本定义,而各个C程序设计环境可能有许多自己的扩充。包括对标准库函数的改写。

//扩充,改写,明显错误,我这里说的是"符合标准的编译器" ,你说不符合标准的编译器那讨论什么


我手头那本
《The C Programming Language》
是建立在unix基础上的~!
//你根本就不懂,你手上的,是第二版吗? 服了你们



不可想象一个库函数会这样写。

//你去怪作者吧,也不是我写的,  不好意思啊,你给我个windows的吧,我看不懂,可能

29 楼

来了几天,就看到 cmaster 一个高手,可惜没机会碰到他

我来回复

您尚未登录,请登录后再回复。点此登录或注册