G
N
I
D
A
O
L

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]);
}
posted @ 2023-03-20 22:11  漫舞八月(Mount256)  阅读(306)  评论(0编辑  收藏  举报