zoukankan      html  css  js  c++  java
  • 关于scanf、getchar、getch、getche缓冲区分析——C语言

    缓冲区

    根据数据刷新的时机可以将缓冲区的类型分为:全缓冲行缓冲无缓冲

    (注意:Windows下的输出设备没有缓冲区,意思是printf是无缓冲的,但是在Linux下printf就是行缓冲的,至于为什么Windows下printf是无缓冲的,后文会提到)

    全缓冲:当缓冲区被填满以后才进行真正的输入输出操作

    行缓冲:当在输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作

    无缓冲:没有缓冲区,立即进行输入输出

    (图片来源:https://www.cnblogs.com/mydomain/p/9817320.html) 

    (缓冲区其实是一块内存空间,它用在硬件设备和用户程序之间,用来缓存数据,目的是让快速的 CPU 不必等待慢速的输入输出设备,同时减少操作硬件的次数)

    为什么Windows下C语言的printf是无缓冲的?

    C语言作为一个面向过程的语言,算不上高级语言也不能说是低级语言,C语言完成的工作大多都是偏底层开发工作(再底层一点可能就是汇编了),C语言常常用在嵌入式、单片机之类的小缓冲区的东西上,

    嵌入式设备的缓冲区通常远小于PC,如果printf是行缓冲的,可能刚写进去一点东西,就可能被别的玩意儿盖掉了,所以嵌入式做开发带流的函数进行I/O的时候通常一次I/O马上就要fflush()

    缓冲区的刷新遵循以下的规则

    1、不管是行缓冲还是全缓冲,缓冲区满时会自动刷新

    2、行缓冲遇到换行符 时会刷新

    3、关闭文件时会刷新缓冲区

    4、程序关闭时一般也会刷新缓冲区,这个是由标准库来保障的

    5、使用特定的函数也可以手动刷新缓冲区

    C语言标准规定,输入输出缓冲区要具有以下特征:

    1、当且仅当输入输出不涉及交互设备时,它们才可以是全缓冲的

    2、错误显示设备不能带有缓冲区

    所谓交互设备,就是现代计算机上的显示器和键盘。C标准虽然规定它们不能是全缓冲的,但并没有规定它们到底是行缓冲还是无缓冲,这就导致不同的平台有不同的实现

    输入设备

    Windows、Linux、Mac OS 在实现时都给输入设备带上了行缓冲,所以 scanf()、getchar()、gets() 在每个平台下的表现都一致

    Windows 下特有的 getche() 和 getch()函数,都不带缓冲区

    输出设备

    Windows 平台下,输出设备是不带缓冲区的

    Linux 和 Mac OS 平台下,输出设备带有行缓冲区

    scanf函数——行缓冲

    这个可能是最常用到的输入函数了,scanf() 是从标准输入设备(键盘)读取数据,当遇到 scanf() 函数时,程序会先检查输入缓冲区中是否有数据,是带有行缓冲区的,如果没有,就等待用户输入,

    用户从键盘输入的每个字符都会暂时保存到缓冲区,直到按下回车键,产生换行符(' '),输入结束

    需要注意的是:当输入的数据与存储该数据的变量类型不符合,scanf() 还会尝试忽略一些空白符,例如空格、制表符、换行符

    例如:

    #include <stdio.h>
    #include <Windows.h>
    
    int main()
    {    
        int a = 10, b = 20;
    
        scanf("%d%d", &a, &b);
        printf("%d %d", a, b);
    
        return 0;
    }

    如果输入1(空格)2,中间的空格会被自动忽略到,也就是上面所说的会尝试忽略掉一些空白符(像前面这样的数字(空格)数字输入格式,无论中间输入多少的空白符会被忽略)

    scanf还有一个比较奇怪的特性:只有当控制字符串以格式控制符开头时,才会忽略换行符、空格、制表符等空白符,为了读者理解这句话的含义,下面上代码

    情况一:不会忽略换行符

     1 #include <stdio.h>
     2 #include <Windows.h>
     3 
     4 int main()
     5 {    
     6     int a = 10, b = 20;
     7 
     8     scanf("%d", &a);
     9     scanf("b=%d", &b);
    10     printf("%d %d", a, b);
    11 
    12     return 0;
    13 }

    当输入1回车时,回车符虽然是属于空白符,但是此时不能被忽略,不能被忽略的换行符(' ')和格式控制字符串%d又不匹配,所以只能读取失败了,b的值不变还是20

    情况二:会忽略换行符

     1 #include <stdio.h>
     2 #include <Windows.h>
     3 
     4 int main()
     5 {    
     6     int a = 10, b = 20;
     7 
     8     scanf("%d", &a);
     9     scanf("%d", &b);
    10     printf("%d %d", a, b);
    11 
    12     return 0;
    13 }

    此时控制字符串变成以格式控制字符串(%d)开头,此时输入1回车,第二个scanf("%d", &b)就会把回车符(' ')忽略掉,等待用户输入下一个数字(在输入1回车后无论输入多少个空格制表符回车都会被忽略掉)

    下面来分析下scanf("%s")输入字符串时的特点

    scanf使用格式控制字符串%s读入字符串时,遇到回车、空格、制表符结束输入,下面将举多个栗子来分析scanf输入字符串时的特点

    例一:

     1 #include <stdio.h>
     2 #include <Windows.h>
     3 
     4 int main()
     5 {    
     6     char ss[2][10];
     7 
     8     scanf("%s%s", ss[0], ss[1]);
     9 
    10     printf("%s%s
    ", ss[0], ss[1]);
    11 
    12     return 0;
    13 }

    输入为:Hello(空格)World

    从结果可以看出,中间的空格被忽略掉了,现在来分析下过程:

    前半部分的 Hello(空格) 碰到空格时就结束第一次输入,将Hello存储在ss[0]这个字符串中(注意:此时空格符仍然存在于缓冲区中,并没有被读走)继续读取第二个字符串,

    此时scanf会先瞧一瞧缓冲区中有没有东西,发现里面有一个空格符,忽略掉,继续读取World,发现后面有个换行符,结束第二次输入,将World存储在ss[1]这个字符串中,换行符仍然留在缓冲区

    (这里的特点和输入单个字符串一致,即在输入第一个非空白符前,输入多少个空格符(空格制表符回车)都会被忽略掉)

     

    getchar()——行缓冲

    getchar()可以看作scanf("%c");的一个简化版本,它和scanf("%c")的特性相同,这里就不再赘述了

    gets()函数——行缓冲

    gets(ss)遇到回车结束输入,读取的换行符被转换为NULL值,做为字符数组的最后一个字符,来结束字符串(意思就是将' '变成了''),gets()以回车作为字符串的终止符,同时将回车符从输入缓冲区读走,

    但不作为字符串的一部分。而scanf()不读走回车符,回车符仍留在输入缓冲区中,除了输入结束标志的不同外,gets和scanf在读取字符串时还有一个很大的区别,gets在读入字符串时不会忽略掉空白符,

    意思是如果有语句gets(ss[0]),如果只按一下回车,那么回车就被当作字符串读入ss[0]这个字符串中,而不像scanf那样在读入到第一个非空白符之前忽略掉输入的所有空白符

     

    代码示例:

     1 #include <stdio.h>
     2 #include <Windows.h>
     3 
     4 int main()
     5 {    
     6     char ss[2][10];
     7 
     8     gets(ss[0]);
     9     gets(ss[1]);
    10     
    11     printf("%s%s", ss[0], ss[1]);
    12 
    13     return 0;
    14 }

     

    getch()函数——不带缓冲区(无缓冲)

    函数功能:当有输入时,立即读取(不需要按回车),不显示在屏幕上(不带回显),需要注意的是这个函数并非标准函数,跨平台使用时需要考虑移植性(getch() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的,

    Linux 和 Mac OS 下没有包含该头文件。换句话说,getche() 并不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用)

    getche()函数——不带缓冲区(无缓冲)

    函数功能:当有输入时,立即读取(不需要按回车),并显示在屏幕上(带回显),和getch()一样位于conio.h中,是非标准函数,认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用

    关于清空缓冲区的三种方法

    在谈到清空缓冲区的方法时,可能很多读者都会想到fflush(stdin)来清空缓冲区,但是fflush(stdin)存在一个问题,就是C语言标准规定,当 fflush() 用于stdout时,必须要有清空输出缓冲区的作用

    但是C语言标准并没有规定 fflush() 用于stdin时的作用,编译器的实现者可以自由决定,所以它的行为是未定义的

    (fflush(stdin) 这种不标准的写法只适用于一部分编译器,通用性比较差)

    方法一:使用getchar()来清空缓冲区

    特点:getchar() 是带有缓冲区的,并且一切字符通吃,或者说一切字符都会读取,不会忽略

    int c;
    while((c = getchar()) != '
    ' && c != EOF);

    方法二:使用scanf()来清空缓冲区

    scanf("%*[^
    ]"); scanf("%*c");

    上面的语句分成两部分来看,第一部分:scanf("%*[^ ]"),这个语句作用是读取缓冲区中回车符(' ')之前的所有字符,并丢弃,在遇到回车符(' ')时便停止读取(注意:此时缓冲区中还有一个回车符),

    第二部分:scanf("%*c"),这个语句的作用是读取一个字符,并丢弃,这样就将缓冲区中仅剩的一个回车符也读走了,此时就已经清空了缓冲区

    方法三:使用fflush(stdin)来清空缓冲区

    fflush(stdin);

    不太推荐使用这种方法(还是因为通用性的问题,建议使用前面两种方法,因为前两种方法在任何平台、任何编译器、任何情景下都有效

  • 相关阅读:
    常见中外出版社
    OpenCL编程基本流程及完整示例
    OpenCL基本概念
    matlab 高阶(三)—— 插值(fft、)
    matlab 高阶(三)—— 插值(fft、)
    matlab 小波处理工具箱
    matlab 小波处理工具箱
    小波图像处理 —— 奇异点(不连续点)检测
    (step6.1.5)hdu 1233(还是畅通工程——最小生成树)
    Android基础总结(精华完整版)
  • 原文地址:https://www.cnblogs.com/lanhaicode/p/10575049.html
Copyright © 2011-2022 走看看