先来看一段代码:
#include<stdio.h>
int main()
{
printf("我不会被输出。。。");
while(1);
}
上面的main方法中只有一个printf函数和一个死循环,作用看似很简单:输出一句话然后进入死循环。但是它的实际运行结果可能会让你很意外:什么也没输出,程序进入了死循环。printf似乎被跳过了?要解释这一现象,就要了解C语言的缓冲区。
什么是缓冲区
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
为什么要引入缓冲区:
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
打个比方,一个搬砖龙鸣工正在工地上搬砖,他肯定会先把砖头放进小推车中,等小推车放满了后再把小推车运走,而不会地上拿起一块砖就开始运。这里的小推车的作用就好比是缓冲区,它大大提高了我们的工作效率。
现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
缓冲区的大小:
如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是4096个字节的大小,这和计算机中的分页机制有关,因为进程在计算机中分配内存使用的就是分页与分段的机制,并且每个页的大小是4096个字节,因此通常情况下缓冲区的大小会设置为4096个字节的大小。
缓冲区的类型
缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。
-
全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。 -
行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。 -
不带缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
输出缓冲区:
当使用printf/puts/puchar等函数显示数据时,并不会直接显示在屏幕上,而是先放入输出缓冲区中(提高程序的运行),当满足一定条件时才会显示在屏幕上:
- 遇到换行符/n,
- 从输出转换到输入状态(即遇到scanf函数)
- 当程序结束(方法正常结束,或者调用exit(0)使程序强制退出)
- 手动刷新,fflush(stdout);
- 当缓冲区满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都无法正确获取了。
在终端输入的数据会先存储到输入缓冲区中,然后再根据占位解析成对应的数据,如果前一次输入的数据有残留的垃圾,会影响后续数据的输入。
- 输入字符(char)时,前一次的输入会残留一个空格或’\n’,解决方法:在%c前加一个空格。
- 如果输入时有若干垃圾数据,会影响后续所有数据的匹配。
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
*/