回 帖 发 新 帖 刷新版面

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

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

沙发

不错,顶~!

板凳

看完了,好就一个字。

3 楼

网上的cFAQ总说使用fflush(stdin)要谨慎,或者说不应使用。我个人认为只要了解了stdin流的原理,在使用起来应该没有问题。
//不能用,行为是未定义的,因为fflush的时候会把缓冲区的所有剩余的东西写回流(如果是输出直接写回输出流,但是是输入,写回键盘? 没有定义!  但是很多实现简单的忽略那些剩余的字符,所以你用起来还正确,但是可能某个系统你的程序就崩溃了)

只是那些“高人”在表述的过程中隐藏了很多的理论细节,或者他们根本自己就不太明白。
//你明白吗? 给我说说为什么读写交替的时候要fseek,说重点就可以了


            fflush(stdin);//如果你使用了这样的?
            scanf("%c", &c2);
            printf("c1 is %c, c2 is %c", c1, c2);
            }


下面深入说明一下:
在程序启动时,会预先定义三个文本
(建立三个文件指针并指定值,但它们不需要显式地打开):
//是什么帮你打开的?

  标准输入流(指针名为stdin)、标准输出流(stdout)和标准错误流(stderr)。stdin 通常与操作系统的标准输入连接,
//这又是谁干的?谁让他们连接的?

在打开时,标准错误流不会完全缓冲;
//为什么不缓冲?
当且仅当流与交互的设备无联系时,标准输入和标准输出流才被完全缓冲(一种常见的方式,与输入设备联系时,比如需要键盘输入数据时,不等输出缓冲区写满就会刷新,此时,就不是完全缓冲)。

//这段话完全是抄袭的,我至少看过,也许是别人抄袭你的

要把外存文件作为输入输出对象,一个可能方式是通过标准输入输出的重新定向,把标准流转接到指定文件。
//原理是什么,谁能重定向?操作系统?
这样做能解决一些问题。但这种


好多错误,一看就不怎么地

4 楼

这样吧,楼上的,你给个使用了fflush(stdin);程序就崩溃了的实例,不是更能说明问题?

5 楼

看清楚点
//不能用,行为是未定义的,因为fflush的时候会把缓冲区的所有剩余的东西写回流(如果是输出直接写回输出流,但是是输入,写回键盘? 没有定义!//
//  但是很多实现简单的忽略那些剩余的字符,所以你用起来还正确,

但是可能某个系统你的程序就崩溃了)
未定义行为你知道是什么吗? 就是出什么事情了C标准不保证你。 当然也可能在一个环境下是正确的,参考K&R

6 楼

不要让我给这个给那个的,我没有时间,有什么问题愿意讨论

7 楼

如果我错了,指点我.

8 楼

不要让我给这个给那个的,我没有时间,有什么问题愿意讨论

=======================================================

1。如果是没时间,就不用讨论了。

2。如果说是“不要让我给这个给那个的”,那么我告诉你,不要用这种口气对个论坛的任何一个人,计算机的知识是广博的,任何一个人都不可能精通所有的有关计算机的知识。你可能在计算机知识的某一个方面有所研究,你不可能知道所有的,这里任何一个人都有可能具备你所不掌握的计算机知识。

3。我是学C的,我用过TC,TC++,C-free,VC,BCB,用得最多的是TC,BCB,运行环境DOS 6.2,WINDOWS 98/me/2003 。其它系统没涉及过。我写过的代码不多,但累计至少也得以数万计。从未碰到使用fflush(stdin);出错的现象。当然,我的接触面是小了一点,代码也写得少,我也同样知道“网上的cFAQ总说使用fflush(stdin)要谨慎,或者说不应使用。”,但不管是自己还是网上我从未见过使用fflush(stdin);引起程序就崩溃的实例。

4。如果你拿不出实例,还讨论什么?

9 楼

说实话我对你是不客气


看吧,看看什么是fflush,记住,参考K&R第二版 中文版 220 页:
原文如下:
int fflush(FILE*stream)
对输出流,fflush函数将已写到缓冲区但尚未写入文件的所有数据写到文件中。对输入流结果是未定义的。
下面是我的解释


下面是aiby的问题
fflush();除了用标准输入流stdin为参数,还可以是什么?  



ssize_t _write(int d, const char *buf, size_t nbytes);
off_t _lseek(int fildes, off_t offset, int whence);


////////////////////////////////////
////////////上面这两个函数是直接的系统调用用来定位和写文件
int
fflush(FILE *stream)
{
    int count, c1, i, retval = 0;

    if (!stream) {////////如果晴空一个空指针,默认就帮你晴空所有的流
        for(i= 0; i < FOPEN_MAX; i++)
        if (__iotab[i] && fflush(__iotab[i]))
                   //这里的i是文件描述符,你可以0 1 2****
            retval = EOF;
        return retval;
    }

    if (!stream->_buf
        || (!io_testflag(stream, _IOREADING)
        && !io_testflag(stream, _IOWRITING)))//这里表示你要清空的流没有发生读写等,没有必要清空,清空就结素了。
        return 0;
    if (io_testflag(stream, _IOREADING)) {
                //正在读,!
        /* (void) fseek(stream, 0L, SEEK_CUR); */
        int adjust = 0;
        if (stream->_buf && !io_testflag(stream,_IONBF))
            adjust = stream->_count;
        stream->_count = 0;
        _lseek(fileno(stream), (off_t) adjust, SEEK_CUR);
        if (io_testflag(stream, _IOWRITE))
            stream->_flags &= ~(_IOREADING | _IOWRITING);
        stream->_ptr = stream->_buf;
        return 0;
    }//正在读,到此,因为清空,所以,所以处理缓冲区。如果缓冲区是输入流(注意) ,那么操作“把缓冲区的文件写回到输入流。。(标准是键盘,怎么办),如果是输出流,直接写入文件旧可以了” 所以,K&R也指出,以输入流为参数是行为无定义的!!!!!!
但是幸运的是 ,操作系统对把缓冲区写入“输入”通常是忽略,(所以你总是对了)
但是可能就有的系统可能报错,这个就是很地层的错误了,我说的写入“输入”出现在write函数,往下看,这里还没有写,只是修改了该修改的标志。

不知道我说明白没有
else if (io_testflag(stream, _IONBF)) return 0;//如果流没有设置缓冲,清空就无所谓了

    if (io_testflag(stream, _IOREAD))        /* "a" or "+" mode */
        stream->_flags &= ~_IOWRITING;

    count = stream->_ptr - stream->_buf;
    stream->_ptr = stream->_buf;

    if ( count <= 0 )
        return 0;

    if (io_testflag(stream, _IOAPPEND)) {
        if (_lseek(fileno(stream), 0L, SEEK_END) == -1) {
            stream->_flags |= _IOERR;
            return EOF;
        }
    }
    c1 = _write(stream->_fd, (char *)stream->_buf, count);
//写入!!!!!!如果stream是stdin,你觉得write输入流是有什么行为??????????????????
    stream->_count = 0;

    if ( count == c1 )
        return 0;

    stream->_flags |= _IOERR;
    return EOF;
}

void
__cleanup(void)//清空所有的流
{
    register int i;

    for(i= 0; i < FOPEN_MAX; i++)
        if (__iotab[i] && io_testflag(__iotab[i], _IOWRITING))
            (void) fflush(__iotab[i]);
}


10 楼

对我不客气没关系,其你对谁不客气,都没关系,这是一个人的素质问题。
好了,我与你讨论就到此。最后我只说一句,你在上面说楼主:
======================
//这段话完全是抄袭的,我至少看过,也许是别人抄袭你的
======================

那么,请你不要只会搬书上的空洞的话来糊弄别人,那些话我也知道,我看过,我还知道Brian W.Kernighan & Dennis M.Ritchie的hello曾是这样写的
main()
{
    printf("hello,world\n");
}

呵呵,晚安,睡觉觉去了.

我来回复

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