缓冲区的概念
在计算机运行过程中,CPU负责对数据的加工和处理,而硬件设备则负责对数据的传输、表示或存储,如打印、输入\输出等。在这一过程中,不同设备的处理速度有较大差别,从而造成一定程度的效率问题。如在打印机输出过程中,若CPU直接将计算结果交由打印机输出,由于打印输出速度较慢,CPU将需要等待上一个结果输出完成后才能继续输出下一个结果。同样,在输入过程中,CPU需要一直等待直到设备I\O将读取的数据交由其处理。在这个过程中,由于CPU速度较I\O设备快,CPU在大部分时间处于等待状态,从而使得CPU利用率很低,造成系统昂贵资源的浪费。
为了解决CPU与外围硬件设备速度不匹配的问题,提高CPU的利用率,提高系统的并行效率,在系统中引入了缓冲区(buffer)的概念。
缓冲区是在物理内存中独立分配的用于数据移动过程中临时存储数据的内存区域。(Wiki Data Buffer)
在使用Buffer之后,数据的传输过程中,可以将数据先放置在缓冲区中,再交由其他设备处理。以上面操作过程为例,CPU可以先将计算结果存放在缓冲区中,再由打印机从其中读取数据,在打印机输出的过程中,CPU可以处理其他数据而无需等待。在输入过程中,CPU不等待输入完成,I\O设备将用户输入存放在缓冲区中,再去请求CPU资源,CPU则可直接从缓冲区中读取数据进行处理,从而大大提高了CPU的利用率和运行效率。
标准I\O的缓冲
标准I\O根据不同的应用需求,提供了全缓冲、行缓冲、无缓冲三种缓冲方式。
全缓冲:只有当划定的缓冲区被填满或者数据读取至末尾时,才开始执行I\O操作(执行系统提供的read\write操作)。磁盘文件的读写一般采用这种方式。
行缓冲:当输入输出过程遇到换行符‘‘\n"或者当分配缓冲区已满时,才开始执行I\O操作。一般涉及终端的读写操作如stdio与stdout使用这种缓冲方式。
无缓冲:当有数据产生时,马上由相应的设备进行处理。一般来说stderr(standard error)使用这种缓冲方式,使得有错误信息时马上能够得到响应。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。
注意,以上关于stdio/stdout的缓冲方式并不是直接规定死的。一些语言的语言规范会对缓冲实现给出一定的限制,但并不具体,只是许多标准I/O是以上述方式实现的而已。可以参考关于流和缓冲区。
I\O函数——getchar
getchar()是用于字符输入的C库函数,其函数的声明包含在头文件stdio.h,函数定义 int getchar(void).
getchar()的功能是读取标准输入stdin中的一个字符。getchar从标准输入中读取数据,而stdin是采用行缓冲的方式记录用户输入,也就是只有当用户键入回车键或输入至缓冲区末尾时,才会开始I\O操作,亦即读取一个字符。这样用户可以一次输入不止一个字符,读取过后缓冲区可能不为空。当再次调用getchar()时,若缓冲区不为空,getchar()就会直接读取在缓冲区中字符,而不是等待用户输入。可以认为是getchar()等待的是行缓冲的完成,而不是用户输入的完成,在行缓冲完成后,只要缓冲区不为空,getchar()就可以读取字符,而不需要等待用户输入。
/*codeblocks13.12*/ #include <stdio.h> int main(void) { char ch = ‘\0‘; while(ch != ‘\n‘) { printf("输入一个字符:"); ch = getchar(); printf("\n"); putchar(ch); printf("\n"); } return 0; }
程序的运行结果如下:
可以明显看到,后续执行中并不要求用户输入,getchar()会直接读取缓冲区中的数据。而且对于字符的读取操作而言,换行符‘\n‘也被视为一个字符,而不是单纯的结束标志。
等待用户输入的字符输入
getchar()可以直接从缓冲区中读取字符,而不等待用户输入,但这种方式也有可能带来潜在的错误。这里给出两种等待用户输入的字符传入方式。
1.使用getche()与getch()函数。都从键盘上读入一个字节,其中后者不将字符回显到屏幕上。以这两个函数读取字符时,都是通过调用函数读取一个键盘输入且只有一个。如调用getche(),键盘敲击‘abc‘时,只有一个字符‘a‘会被读取。其他字符为无效输入。
2.在每次getchar()之后,手动对缓冲区进行清除操作。可以使用fflush()函数清理缓冲区。C标准规定 fflush()函数可用来刷新输出(stdout)缓冲区(一般是将缓冲区数据写回存储设备)。但对于标准输入(stdin)则没有明确定义。部分编译器定义了 fflush( stdin )的实现,如微软的VC。也就是不同的编译器对于 fflush( stdin )的支持可能不同。GCC编译器没有定义它的实现,所以不能使用 fflush( stdin )来刷新输入缓冲区。