C语言之缓冲区

先来看一段代码:

 #include<stdio.h>
 int main()
 {
     printf("我不会被输出。。。");
	 while(1);
 }

上面的main方法中只有一个printf函数和一个死循环,作用看似很简单:输出一句话然后进入死循环。但是它的实际运行结果可能会让你很意外:什么也没输出,程序进入了死循环。printf似乎被跳过了?要解释这一现象,就要了解C语言的缓冲区。

什么是缓冲区

缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区输出缓冲区

为什么要引入缓冲区:
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
打个比方,一个搬砖龙鸣工正在工地上搬砖,他肯定会先把砖头放进小推车中,等小推车放满了后再把小推车运走,而不会地上拿起一块砖就开始运。这里的小推车的作用就好比是缓冲区,它大大提高了我们的工作效率。

现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

缓冲区的大小:
如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是4096个字节的大小,这和计算机中的分页机制有关,因为进程在计算机中分配内存使用的就是分页与分段的机制,并且每个页的大小是4096个字节,因此通常情况下缓冲区的大小会设置为4096个字节的大小。

缓冲区的类型

缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。

  1. 全缓冲
    在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。

  2. 行缓冲
    在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。

  3. 不带缓冲
    也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

输出缓冲区:

当使用printf/puts/puchar等函数显示数据时,并不会直接显示在屏幕上,而是先放入输出缓冲区中(提高程序的运行),当满足一定条件时才会显示在屏幕上:

  1. 遇到换行符/n,
  2. 从输出转换到输入状态(即遇到scanf函数)
  3. 当程序结束(方法正常结束,或者调用exit(0)使程序强制退出)
  4. 手动刷新,fflush(stdout);
  5. 当缓冲区满4k时自动输出。

至此,我们应该能解释文章开头代码的printf为什么没有输出到控制台了:运行到printf方法后,编译器将数据缓存到了内存的缓冲区中等待被输出。不巧的是,我们的程序不满足它输出的条件,因此它并没有输出,它只是一直存在于缓冲区中。

我们可以把程序改写成下面这样(使用任意一个方法都可以使printf正确输出了):

#include <stdio.h>
int main()
{
	int n;
	printf("我能被正确输出啦!!!");
	//printf("\n");							方法1
	//scanf("%d",&n);						方法2
	//exit(0);								方法3
	//fflush(stdout);						方法4
	for(;;)
	{
		//printf("我会不断地填充缓冲区");		方法5
	}
}

输入缓冲区:

在了解了输出缓冲区后,我们再来看看输入缓冲区的问题:

#include<stdio.h>
int main()
{
	int i = 0;
	char c = '0';
	scanf("%d", &i);
	scanf("%c", &c);
	printf("-%d-\n", i);
	printf("-%c-\n", c);
}
/*
in:1←
out:-1-
	-
	-
in:1 b←
out:-1-
	- -
*/

main方法中定义了两个变量,然后从控制台输入它们的值,再输出它们。当我们在控制台输入1再按回车后,发现程序结束了并输出了。从输出结果来看,很明显是因为回车符被当成了字符输入。解决这种问题的方法就是在%c前加一个空格符。

下面我们再看一段代码:

#include<stdio.h>
int main(){
	int a = 0;
	int b = 0;
	scanf("%d",&a);
	scanf("%d",&b);
	printf("-%d-\n",a);
	printf("-%d-\n",b);
}
/*
in:a←
out:-0-
	-0-
*/

这次我们直接输入a再回车,发现程序结束并且输出了0和0。当输入的有垃圾或错误数据时,a和b都无法正确获取了。

在终端输入的数据会先存储到输入缓冲区中,然后再根据占位解析成对应的数据,如果前一次输入的数据有残留的垃圾,会影响后续数据的输入。

  1. 输入字符(char)时,前一次的输入会残留一个空格或’\n’,解决方法:在%c前加一个空格
  2. 如果输入时有若干垃圾数据,会影响后续所有数据的匹配。
    1. 使用正则表达式,必须确定有垃圾数据时再使用。
    scanf("%*[^\n]"); //从缓冲区中获取数据但不存储到变量中
    scanf("%*c");
    2. 设置缓冲区中的位置指针,可以用来清空缓冲区。
    stdin->_IO_read_prt 开始位置
    stdin->_IO_read_end 结束位置。
    stdin->_IO_read_prt = stdin->_IO_read_end

要解决上方代码的问题,可以采用这种方法(此时,第二个数据就能被正确接收了)

#include <stdio.h>

int main()
{
	int num = 0;
	if(0 == scanf("%d",&num))		//scanf的返回值表示正确接受的数据的数量
	{
		//scanf("%*[^\n]");
		//scanf("%*c");
		stdin->_IO_read_ptr = stdin->_IO_read_end;
	}
	
	int num1 = 0; 
	scanf("%d",&num1);
	printf("%d %d\n",num,num1);
}
/*
in:a←
   5←
out:0 5
*/
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值