C语言scanf函数的匹配和缓冲机制
scanf函数的数据匹配规则
数据类型 | 匹配规则 |
---|---|
整型%d |
两个数据间的分割符可以是空格符(ASCII=32)、换行符(ASCII=10)、制表符(ASCII=9) |
浮点型%f |
两个数据间的分割符可以是空格符(ASCII=32)、换行符(ASCII=10)、制表符(ASCII=9) |
字符串%s |
以非空白字符开始,以第一个空白字符(空格符、换行符、制表符)结束 |
字符型%c |
两个数据间没有分割符,所有字符(包括控制字符)都能匹配 |
缓冲区的三种类型
缓冲区的三种类型:
- 全缓冲:在这种情况下,当填满标准 I/O 缓存后才进行实际 I/O 操作。全缓冲的典型代表是对磁盘文件的读写。
- 行缓冲:在这种情况下,当在输入和输出中遇到换行符时,才执行真正的 I/O 操作。这时,输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的 I/O 操作。典型代表是键盘输入数据,标准输入(stdin)和标准输出(stdout)。
- 不带缓冲:也就是不进行缓冲,标准出错情况 stderr 是典型代表,这使得出错信息可以直接尽快地显示出来。
以下情况将引发缓冲区的刷新:
- 缓冲区满时;
- 行缓冲区遇到换行符 \n;
- 关闭文件;
- 使用特定函数刷新缓冲区。
scanf 函数的原理就是行缓冲!大家应该都有经验吧,比如输入一行数据,这些数据并非刚输入完就能被程序读取,还需要你按下回车键才能读取到。不过需要注意,用户最后输入的回车也会储存在缓冲区中。
举例
例子来源C语言输入输出缓冲区:
#include<stdio.h>
#include<Windows.h>
int main() {
printf("hello");
int i = 0;
for (; i < 10; i++) {
putchar('a');
sleep(1000); // 延时 1 秒钟
}
return 0;
}
运行该程序,等待 10s 后,输出 helloaaaaaaaaaa。
这是由于键盘输入是标准的行缓冲,只有遇到换行符或者程序结束后才会输出到屏幕上。
#include<stdio.h>
#include<Windows.h>
int main() {
printf("hello");
fflush(stdout); // 刷新缓冲区,使得hello被强制输出,现在缓冲区什么都没有了
int i = 0;
for (; i < 10; i++) {
putchar('a');
sleep(1000); // 延时 1 秒钟
}
return 0;
}
运行该程序,首先输出 hello,等待 10s 后,继续输出 aaaaaaaaaa。这是由于 fflush 刷新了缓冲区,所以会首先输出 hello。
现在用几道题目来解释缓冲机制,顺便加深对匹配规则的理解!
实战:通过几个问题搞明白缓冲机制
问题 1:两个整型数据之间没有空格?
【语句】
scanf("%d%d", &a, &b);
【输入】
1 2
【匹配情况】
a = 1, b = 2
【输入缓冲区分析】请注意,这是当你输入完数据并按下回车键后输入缓冲区里的情况,不能将运行窗口的输入输出混为一谈。它们不是同一个东西!
匹配前:
1 2\n
匹配后:
\n
【结论】连续输入两个数值时,两个数值之间必须有空格,无论 %d 是否紧挨着。scanf 函数就是靠空格、换行等匹配不同的数值,但匹配完后不会将换行符吸收掉。该语句等同于以下语句:
scanf("%d %d", &a, &b);
问题 2:读取字符型数据时会把空格读入吗?
【语句】
scanf("%c%c", &a, &b);
【输入 1】
xy
【匹配情况 1】
a = 'x', b = 'y'
【输入缓冲区分析 1】
- 匹配前:
xy\n
- 匹配后:
\n
【输入 2】
x y
【匹配情况 2】
a = 'x', b = ' '
【输入缓冲区分析 2】
- 匹配前:
x y\n
- 匹配后:
y\n
如果此时再来一个scanf("%c%c", &c, &d)
,则c = 'y', d = ' '
。
【结论】开始匹配字符型数据时,scanf 函数不会忽略空格。如果你确实需要空格间隔,需改成:
scanf("%c %c", &a, &b);
问题 3:字符型数据和整型数据需要空格吗?
【语句】
scanf("%d%c", &a, &b);
【输入 1】
11y
【匹配情况 1】
a = 11, b = 'y'
【输入缓冲区分析 1】
- 匹配前:
11y\n
- 匹配后:
\n
【输入 2】
11 y
【匹配情况 2】
a = 11, b = ' '
【输入缓冲区分析 2】
- 匹配前:
11 y\n
- 匹配后:
y\n
【结论】当匹配完前一个数值后,scanf 函数在给出的格式中发现下一个数据是字符型数据,于是开始匹配字符型数据,此时不会忽略空格。如果你确实需要空格间隔,需改成:
scanf("%d %c", &a, &b);
问题 4:字符型数据之间的换行?(1)
【语句】
scanf("%c%c", &a, &b);
【输入】
x
y
【匹配情况】
a = 'x', b = ' '
【输入缓冲区分析】
- 匹配前:
x\n
y\n
- 匹配后:
y\n
【结论】scanf 函数在匹配字符型数据时,不仅匹配空格,还匹配任何字符,包括控制字符。所以字符 b 读入了换行字符 \n。
问题 5:字符型数据之间的换行?(2)
【语句】
scanf("%c", &a);
printf("a=%c\n", a);
scanf("%c", &b);
printf("b=%c", b);
【输入 1】
x
【输出 1】然而,当你输入完 a,按下回车,输出 a 的同时,也输出了 b。
a = x
b =
【输入缓冲区分析 1】
- 第一次匹配前:
x\n
- 第一次匹配后,并输出 a 的值为“x”,第二次匹配前:
\n
- 第二次匹配后,b 匹配到了换行符 \n:
【输入 2】
xy
【输出 2】
a = x
b = y
【输入缓冲区分析 2】
- 第一次匹配前:
xy\n
- 第一次匹配后,并输出 a 的值为“x”,第二次匹配前:
y\n
- 第二次匹配后,b 匹配到了“y”:
\n
问题 6:两个整型数据之间多个空行?
【语句】
scanf("%d%d", &a, &b);
【输入】
1
(换行)
(换行)
2
【匹配情况】
a = 1, b = 2
【输入缓冲区分析】
第一个匹配前:
1\n
当你按了三次回车、并输入 2 前,其实第一个已经得到了匹配:
\n
\n
\n
第二个匹配前:
\n
\n
\n
2\n
第二个得到匹配后:
\n
\n
\n
\n
【结论】scanf 函数就是靠空格、换行等匹配不同的数值,而且必须严格按照给出的格式进行匹配(比如%d%d
必须要两个整数,少一个都不行)。在没有匹配完成之前,无论有多少个空格和换行,都要等待用户输入下一个数据。
一切的罪魁祸首:原来是缓冲区还有多余的数据!
为什么实际的运行情况跟设想的不一样?为什么这个字符变量读到的是换行符?原来一切的罪魁祸首是:缓冲区还有多余的数据!缓冲区并没有设想的那样干净,而是残留了一些不该有的东西!那么如何解决呢?
方法一:用 getchar() 把换行符吸收掉
【语句】
scanf("%c", &a);
printf("a=%c\n", a);
getchar();
scanf("%c", &b);
printf("b=%c", b);
【输入和输出】
x
a = x
y
b = y
【输入缓冲区分析】
- 输入 a,第一次匹配前:
x\n
- 第一次匹配后,并输出 a 的值为“x”:
\n
- 执行 getchar,读取了换行符 \n,此时缓冲区为空:
- 输入 b,第二次匹配前:
y\n
- 第二次匹配后,b 匹配到了“y”:
\n
方法二:使用 fflush(stdin) 把输入缓冲区刷新一遍
需要注意,fflush 在有些编译器是没有的,它不属于 C 标准。不推荐使用。
【语句】
scanf("%c", &a);
printf("a=%c\n", a);
fflush(stdin); // 清理输入缓冲区
scanf("%c", &b);
printf("b=%c", b);
【输入和输出】
输入:x
输出:a = x
输入:y
输出:b = y
【输入缓冲区分析】
- 输入 a,第一次匹配前:
x\n
- 第一次匹配后,并输出 a 的值为“x”:
\n
- 执行 fflush,此时缓冲区为空:
- 输入 b,第二次匹配前:
y\n
- 第二次匹配后,b 匹配到了“y”:
\n
实战:空一行表示输入完毕
【描述】每行输入两个字符串,当输入到空行时,表示输入完毕。请输出这些字符串。
【输入实例】
aaa bbb
ccc ddd
eee fff
(该行空行,表示输入完毕)
该输入等价于:
aaa bbb\nccc ddd\neee fff\n\n
当读到只有 \n 的一行时,表示输入完毕。
【输出实例】
aaa bbb
ccc ddd
eee fff
【分析】这题很考验你对输入缓冲区原理的理解。每次检测字符串 A 的第一个字符,若第一个字符是 \n 则停止输入;若不是 \n,则用另一个字符串变量 B 读取剩下的字符串,并将 B 拼接到 A 的后面。除此之外,每读入一行字符串,就要吸收掉最后的 \n。
【题解】
#include <cstdio>
#include <cstring>
using namespace std;
int main(){
char word[20][20] = {0};
char mean[20][20] = {0};
char tmp[20];
int i, j;
for (i = 0; ; i++){
if ((word[i][0] = getchar()) == '\n') //检测第一个字符
break;
word[i][1] = '\0';
scanf("%s", tmp); // 读入 word 的剩余字符
scanf("%s", mean[i]);
strcat(word[i], tmp); // word 的第一个字符与剩余字符拼接在一起
tmp[0] = '\0'; // 清空字符串
getchar(); // 吸收换行符
}
for (j = 0; j < i; j++)
printf("%s %s\n", word[j], mean[j]);
}