zoukankan      html  css  js  c++  java
  • fflush 函数

    1、C99 对 fflush 函数的定义:


    int fflush(FILE *stream);

    如果stream指向输出流或者更新流(update stream),并且这个更新流最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。
    fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush函数会给那些流打上错误标记,并且返回EOF,否则返回0。

    由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin) 是不正确的,至少是移植性不好的。

    ,成功返回0,出错返回EOF。

    2、使用注意点及误区

    为什么 fflush(stdin) 是错的?
    首先请看以下程序:
    Quote:
    程序代码 程序代码
    #i nclude
    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) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(gcc3.2不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着 fflush on input stream is an extension to the C standard (fflush 操作输入流是对C标准的扩充)。当然,如果你毫不在乎程序的移植性,用  fflush(stdin) 也没什么大问题。

    2.      清空输入缓冲区的方法

    虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。
    Quote:程序代码 程序代码
    /* C 版本 */
    #i nclude

    int main( void )
    {
        int i, c;
        for (;;) {
            fputs("Please input an integer: ", stdout);
            if ( scanf("%d", &i) != EOF ) { /* 如果用户输入的不是 EOF */
                /* while循环会把输入缓冲中的残留字符清空 */
                /* 读者可以根据需要把它改成宏或者内联函数 */
                /* 注:C99中也定义了内联函数,gcc3.2支持 */
                while ( (c=getchar()) != '/n' && c != EOF ) {
                      ;
                } /* end of while */
            }
            printf("%d/n", i);
        }
        return 0;
    }


    运行结果:
    当输入数字时,正常;
    当输入字母时,i的值不改变,printf输出的仍是上一次的值。由于输入缓冲区每次都被清空,所以也就不存在scanf自动不停读取缓冲区的值的情况。


    #include   
    #include   
    
    void   main() 
    { 
            int   n   =   5; 
            int   i   ; 
            char   c[5]; 
            for(i   =   0;   i   <   n;   i++) 
            { 
                    c[i]=scanf( "   %c ",&c[i]); 
                  /*   fflush(stdin);*/ 
                    /*printf( "%c
     ",c[i])   ;*/ 
            } 
    
            for(i=0;i <n;i++) 
            printf( "%d ",c[i]); 
            getch(); 
    } 



    我们在运行程序的时候,输入如下 




    程序便宣告结束,而实际上程序的循环有五次,我们应该输入5次程序才会结束的,这是什么原因呢?我们先看看c语言中的输入缓冲:C语言编译器I/O输入函数一般采用了缓冲式输入方式。也就是说,当你输入“a回车b回车c回车”时,程序先输入a但同时将回车缓冲区里,然后下次再从缓冲区里取出数据,也就是回车。依次类推。可以用getchar()解决问题。根据上面的介绍,   我们再来分析我们的输入过程: 

    a   回车 

    b回车 



    恰好从缓冲区内读取了5次,因此我们的程序宣告结束了。有什么方法解决这个问题呢? 

    1、在每次读取之后,将缓冲区刷新——使用fflush(stdin)函数; 

    2、scanf( "%c ",&c[i])   ;   改成scanf( "   %c ",&c[i])   ;注意在 "%c "前面加上一个空格; 

    3、完全杜绝这种情况发生,使用gets()函数,或者scanf( "%s ",c)   ;一次性作为一个字符串读入; 

    下面再介绍一下getch,getche(),getchar();的区别,这几个函数的使用实在是太多了,这三个函数的区别一直都不清楚,今天终于有机会弄明白了: 

    getch()和getche()函数   
     这两个函数都是从键盘上读入一个字符。其调用格式为:   
      getch();   
      getche();   
     两者的区别是:getch()函数不将读入的字符回显在显示屏幕上,而getche()  函数却将读入的字符回显到显示屏幕上。   

    getchar()函数   

     getchar()函数也是从键盘上读入一个字符,   并带回显。它与前面两个函数的区别在于:   getchar()函数等待输入直到按回车才结束,    

     回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。 



    /------------------------------------------------------------------------------------------------------------------------------------------------------/
    2.10. C标准库的I/O缓冲区 请点评

    用户程序调用C标准I/O库函数读写文件或设备,而这些库函数要通过系统调用把读写请求传给内核(以后我们会看到与I/O相关的系统调用),最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc/fputc为例,当用户程序第一次调用fgetc读一个字节时,fgetc函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc函数会再次进入内核读1K字节到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在第 5 节 “Memory Hierarchy”中CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。

    下图以fgets/fputs示意了I/O缓冲区的作用,使用fgets/fputs函数时在用户程序中也需要分配缓冲区(图中的buf1和buf2),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。

    图 25.1. C标准库的I/O缓冲区








    C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时,不同类型的缓冲区具有不同的特性。

    全缓冲

    如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。

    行缓冲

    如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。

    无缓冲

    用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。

    下面通过一个简单的例子证明标准输出对应终端设备时是行缓冲的。

    #include 

    int main()
    {
    printf("hello world");
    while(1);
    return 0;
    }

    运行这个程序,会发现hello world并没有打印到屏幕上。用Ctrl-C终止它,去掉程序中的while(1);语句再试一次:

    $ ./a.out
    hello world$

    hello world被打印到屏幕上,后面直接跟Shell提示符,中间没有换行。

    我们知道main函数被启动代码这样调用:exit(main(argc, argv));。main函数return时启动代码会调用exit,exit函数首先关闭所有尚未关闭的FILE *指针(关闭之前要做Flush操作),然后通过_exit系统调用进入内核退出当前进程[35]

    在上面的例子中,由于标准输出是行缓冲的,printf("hello world");打印的字符串中没有换行符,所以只把字符串写到标准输出的I/O缓冲区中而没有写回内核(写到终端设备),如果敲Ctrl-C,进程是异常终止的,并没有调用exit,也就没有机会Flush I/O缓冲区,因此字符串最终没有打印到屏幕上。如果把打印语句改成printf("hello world ");,有换行符,就会立刻写到终端设备,或者如果把while(1);去掉也可以写到终端设备,因为程序退出时会调用exitFlush所有I/O缓冲区。在本书的其它例子中,printf打印的字符串末尾都有换行符,以保证字符串在printf调用结束时就写到终端设备。

    我们再做个实验,在程序中直接调用_exit退出。

    #include 
    #include 

    int main()
    {
    printf("hello world");
    _exit(0);
    }

    结果也不会把字符串打印到屏幕上,如果把_exit调用改成exit就可以打印到屏幕上。

    除了写满缓冲区、写入换行符之外,行缓冲还有两种情况会自动做Flush操作。如果:

    • 用户程序调用库函数从无缓冲的文件中读取

    • 或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据

    那么在读取之前会自动Flush所有行缓冲。例如:

    #include 
    #include 

    int main()
    {
    char buf[20];
    printf("Please input a line: ");
    fgets(buf, 20, stdin);
    return 0;
    }

    虽然调用printf并不会把字符串写到设备,但紧接着调用fgets读一个行缓冲的文件(标准输入),在读取之前会自动Flush所有行缓冲,包括标准输出。//对于交互式行缓冲来说可以自己flush,但是其他的不一定,所以小心使用自动flush的功能,必要时自己用fflush操作

    如果用户程序不想完全依赖于自动的Flush操作,可以调fflush函数手动做Flush操作。

    #include 

    int fflush(FILE *stream);
    返回值:成功返回0,出错返回EOF并设置errno

    对前面的例子再稍加改动:

    #include 

    int main()
    {
    printf("hello world");
    fflush(stdout);
    while(1);
    }

    虽然字符串中没有换行,但用户程序调用fflush强制写回内核,因此也能在屏幕上打印出字符串。fflush函数用于确保数据写回了内核,以免进程异常终止时丢失数据。作为一个特例,调用fflush(NULL)可以对所有打开文件的I/O缓冲区做Flush操作。

  • 相关阅读:
    最值
    算数平均值/加权平均值
    jdbatemplate使用
    所有正规jar包
    java代码在计算机中经历的三个阶段,反射的原理
    lambda函数式编程思想
    重写equals方法
    安全牛-弱点扫描
    安全牛-主动收集
    安全牛-被动收集
  • 原文地址:https://www.cnblogs.com/lzq1126/p/5596840.html
Copyright © 2011-2022 走看看