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语言输入输出缓冲区:
1 2 3 4 5 6 7 8 9 10 11
| #include<stdio.h> #include<Windows.h> int main() { printf("hello"); int i = 0; for (; i < 10; i++) { putchar('a'); sleep(1000); } return 0; }
|
运行该程序,等待 10s 后,输出 helloaaaaaaaaaa。
这是由于键盘输入是标准的行缓冲,只有遇到换行符或者程序结束后才会输出到屏幕上。
1 2 3 4 5 6 7 8 9 10 11 12
| #include<stdio.h> #include<Windows.h> int main() { printf("hello"); fflush(stdout); int i = 0; for (; i < 10; i++) { putchar('a'); sleep(1000); } return 0; }
|
运行该程序,首先输出 hello,等待 10s 后,继续输出 aaaaaaaaaa。这是由于 fflush 刷新了缓冲区,所以会首先输出 hello。
现在用几道题目来解释缓冲机制,顺便加深对匹配规则的理解!
实战:通过几个问题搞明白缓冲机制
问题 1:两个整型数据之间没有空格?
【语句】
【输入】
【匹配情况】
【输入缓冲区分析】请注意,这是当你输入完数据并按下回车键后输入缓冲区里的情况,不能将运行窗口的输入输出混为一谈。它们不是同一个东西!
匹配前:
匹配后:
【结论】连续输入两个数值时,两个数值之间必须有空格,无论 %d 是否紧挨着。scanf 函数就是靠空格、换行等匹配不同的数值,但匹配完后不会将换行符吸收掉。该语句等同于以下语句:
问题 2:读取字符型数据时会把空格读入吗?
【语句】
【输入 1】
【匹配情况 1】
【输入缓冲区分析 1】
【输入 2】
【匹配情况 2】
【输入缓冲区分析 2】
如果此时再来一个scanf(“%c%c”, &c, &d),则c = ‘y’, d = ’ '。
【结论】开始匹配字符型数据时,scanf 函数不会忽略空格。如果你确实需要空格间隔,需改成:
问题 3:字符型数据和整型数据需要空格吗?
【语句】
【输入 1】
【匹配情况 1】
【输入缓冲区分析 1】
【输入 2】
【匹配情况 2】
【输入缓冲区分析 2】
【结论】当匹配完前一个数值后,scanf 函数在给出的格式中发现下一个数据是字符型数据,于是开始匹配字符型数据,此时不会忽略空格。如果你确实需要空格间隔,需改成:
问题 4:字符型数据之间的换行?(1)
【语句】
【输入】
【匹配情况】
【输入缓冲区分析】
【结论】scanf 函数在匹配字符型数据时,不仅匹配空格,还匹配任何字符,包括控制字符。所以字符 b 读入了换行字符 \n。
问题 5:字符型数据之间的换行?(2)
【语句】
1 2 3 4 5
| scanf("%c", &a); printf("a=%c\n", a); scanf("%c", &b); printf("b=%c", b);
|
【输入 1】
【输出 1】然而,当你输入完 a,按下回车,输出 a 的同时,也输出了 b。
【输入缓冲区分析 1】
- 第一次匹配后,并输出 a 的值为“x”,第二次匹配前:
【输入 2】
【输出 2】
【输入缓冲区分析 2】
- 第一次匹配后,并输出 a 的值为“x”,第二次匹配前:
问题 6:两个整型数据之间多个空行?
【语句】
【输入】
【匹配情况】
【输入缓冲区分析】
第一个匹配前:
当你按了三次回车、并输入 2 前,其实第一个已经得到了匹配:
第二个匹配前:
第二个得到匹配后:
【结论】scanf 函数就是靠空格、换行等匹配不同的数值,而且必须严格按照给出的格式进行匹配(比如%d%d必须要两个整数,少一个都不行)。在没有匹配完成之前,无论有多少个空格和换行,都要等待用户输入下一个数据。
一切的罪魁祸首:原来是缓冲区还有多余的数据!
为什么实际的运行情况跟设想的不一样?为什么这个字符变量读到的是换行符?原来一切的罪魁祸首是:缓冲区还有多余的数据!缓冲区并没有设想的那样干净,而是残留了一些不该有的东西!那么如何解决呢?
方法一:用 getchar() 把换行符吸收掉
【语句】
1 2 3 4 5 6
| scanf("%c", &a); printf("a=%c\n", a); getchar(); scanf("%c", &b); printf("b=%c", b);
|
【输入和输出】
【输入缓冲区分析】
- 执行 getchar,读取了换行符 \n,此时缓冲区为空:
方法二:使用 fflush(stdin) 把输入缓冲区刷新一遍
需要注意,fflush 在有些编译器是没有的,它不属于 C 标准。不推荐使用。
【语句】
1 2 3 4 5 6
| scanf("%c", &a); printf("a=%c\n", a); fflush(stdin); scanf("%c", &b); printf("b=%c", b);
|
【输入和输出】
1 2 3 4
| 输入:x 输出:a = x 输入:y 输出:b = y
|
【输入缓冲区分析】
实战:空一行表示输入完毕
【描述】每行输入两个字符串,当输入到空行时,表示输入完毕。请输出这些字符串。
【输入实例】
1 2 3 4
| aaa bbb ccc ddd eee fff (该行空行,表示输入完毕)
|
该输入等价于:
1
| aaa bbb\nccc ddd\neee fff\n\n
|
当读到只有 \n 的一行时,表示输入完毕。
【输出实例】
【分析】这题很考验你对输入缓冲区原理的理解。每次检测字符串 A 的第一个字符,若第一个字符是 \n 则停止输入;若不是 \n,则用另一个字符串变量 B 读取剩下的字符串,并将 B 拼接到 A 的后面。除此之外,每读入一行字符串,就要吸收掉最后的 \n。
【题解】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #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); scanf("%s", mean[i]); strcat(word[i], tmp); tmp[0] = '\0'; getchar(); } for (j = 0; j < i; j++) printf("%s %s\n", word[j], mean[j]); }
|