当我们运行这样的程序:
#include <stdio.h>
int main() {
int a, b;
while(scanf("%d%d", &a, &b)) {
printf("a : %d, b = %d\n", a, b);
}
return 0;
}
该程序会接受两个输入,赋值给a
和b
,然后输出它们的值:
1 2
a : 1, b : 2
3 4
a : 3, b : 4
我们再进行这样的测试:
1 2 3
a : 1, b : 2
4
a : 3, b : 4
发现我们在第一次输入中多余的内容,会在第二次输出。我们发现这个模型与一个队列(queue)相似:
第一次向队列里输入三个元素1,2,3;
queue.push(1);
queue.push(2);
queue.push(3);
此时的队列:队列尾端$\to\begin{bmatrix}3&2&1\end{bmatrix}\to$队列首端。
scanf
函数让队列推出两个元素,按顺序赋值给a
和b
:
a = queue.pop();
b = queue.pop();
此时的队列:队列尾端$\to\begin{bmatrix}3\end{bmatrix}\to$队列首端,第一个被推出的是$1$,所以a
被赋为1,第二个被推出的是$2$,所以b
被赋值为2。
第二次输入4:
queue.push(4);
此时的队列:队列尾端$\to\begin{bmatrix}4&3\end{bmatrix}\to$队列首端。
scanf
函数再次让队列推出两个元素,按顺序赋值给a
和b
:
a = queue.pop();
b = queue.pop();
此时的队列:队列尾端$\to\begin{bmatrix}\end{bmatrix}\to$队列首端,第一个被推出的是$3$,所以a
被赋为3,第二个被推出的是$4$,所以b
被赋值为4。
缓冲区(buffer)就是这样的队列。
我们在测试程序中,第一次输入的三个数,那如果是300个数,3000个数甚至是30000个数呢?我们知道计算机中的资源是有限的,缓冲区这个队列肯定也是有长度限制的,如果我们往缓冲区中推入过量的数据,就会产生缓冲区溢出(buffer overflow)。当然,上面所举的例子是输入流的缓冲区,实际的计算机系统里到处都是缓冲区:堆、栈都是一个缓冲区,都会发生缓冲区溢出。
相比于缓冲区溢出,在与输入输出相关的情况下,更让人烦恼的其实还是缓冲区残余内容对后续输入的影响,我们再举一个输入相关的例子,下面的程序是输入数组,然后求他们的平均数:
int main() {
int n;
while(cin >> n) {
double sum = 0;
double temp;
for(int i = 0;i < n; i++) {
cin >> temp;
sum += temp;
}
cout << sum / (double)n << endl;
}
}
加入用户第一次想输入三个数字,但一不小心输入了四个数字,那么第四个数字就会被赋给n
影响后续的计算:
3
1 2 3 4
2
3 1 2 3
2.25
第二次计算的是4个数字的均值sum(3, 1, 2, 3) / 4
,但用户想要计算三个数字的均值sum(1, 2, 3) / 3
,这样无疑会影响程序使用者的用户体验。因此在每次输入后往往需要清除缓冲区。
C语言中使用fflush(stdin)
语句来清除输入缓冲区,我们可以将其加在程序中查看效果:
int main() {
int n;
while(cin >> n) {
double sum = 0;
double temp;
for(int i = 0;i < n; i++) {
cin >> temp;
sum += temp;
}
cout << sum / (double)n << endl;
fflush(stdin);
}
}
采用相同的输入,查看测试结果:
3
1 2 3 4
2
3 1 2 3
2
发现第一次输入中多余的部分被舍弃。第二次计算的结果是符合预期的。
再看一个简单的程序:
#include <stdio.h>
int main()
{
int i;
for (;;) {
fputs("Please input an integer: ", stdout);
scanf("%d", &i);
printf("%d\n", i);
// fflush(stdin);
}
return 0;
}
你可以输入一个字母,或是一个小数,看看会输出什么,然后在注释的位置加入fflush(stdin)
,再进行相同的输入,观察结果变成了什么。
而实际上,==这种用法是不规范的,至少是移植性不好的==,首先是有些编译器并不支持该用法,第二是如果stream指向输入流(如 stdin),那么fflush函数的行为是不确定的。这点可以参考C99
手册(Linux环境下命令行输入man fflush
)即可查阅。
我们可以在scanf
后面增加一些内容来实现缓冲区的清除:
#include <stdio.h>
int main()
{
int i;
for (;;) {
fputs("Please input an integer: ", stdout);
if (scanf("%d", &i) != EOF) { /* 如果用户输入的不是 EOF */
/* while循环会把输入缓冲中的残留字符清空 */
/* 读者可以根据需要把它改成宏或者内联函数 */
while ((c = getchar()) != '\n' && c != EOF) {
;
}
printf("%d\n", i);
}
}
return 0;
}
C++中我们会先调用cin.clear()
来清除输入流的错误标记,然后调用
cin.ignore( std::numeric_limits<std::streamsize>::max( ), '\n' );
来清理缓冲区。