两次调用scanf函数的问题

两次调用scanf函数可能存在的问题

1.问题描述

scanf()函数可以从stdin中读取数据并写入指定的内存地址处。它的工作原理大致如下:

  1. 程序运行至scanf()处时,scanf()检查stdin是否有数据。
  2. 如果stdin中有数据则按照格式串的格式读取数据并写入给定的内存地址处。如果stdin中没有数据,则阻塞等待用户输入。
  3. 将用户的第一个非空白符输入(即不算空格和回车)与格式串中的第一个格式做匹配,只要输入的格式与scanf()格式串中规定的格式不一致,则scanf()函数立马结束。
  4. 如果在非空白符后输入空白符(如空格、回车),则表明接下来的输入与格式串的下一个格式做匹配。
  5. 如果按照格式串的格式,顺序接收到了与之匹配的输入项,则scanf()函数结束。

空白符就是空白字符的意思,通俗理解就是不会显示出来的字符,类似空格符、回车换行符、制表符等,从视觉效果来看,就是一个空白区域。

发生问题的地方就在于,第二次调用scanf()时,它的格式串中第一个格式如果是字符格式,即scanf("%c ...", &char, ...);,将可能导致第二次scanf()调用失败,即如果你在程序中调用了两次scanf(),在程序运行时,它只正确读取了你第一个输入值,然后程序就结束了。如果此时程序有打印,会发现只打印了第一个输入的值,没有打印第二个输入值。具体来看下面的程序演示。

2.程序演示

在程序中调用两次scanf(),第一次scanf()输入格式为一个整数,第二次scanf()输入格式为一个字符。最后打印两次输入的值。

#include <stdio.h>

int main()
{
	int a;
	char c;

	puts("Please input value:");
	scanf("%d", &a);
	scanf("%c", &c);

	printf("%d %c\n", a, c);

	return 0;
}

然后运行程序。这里我们进行两次不同的输入

(1)第一次输入:5 Space c

space表示按下空格键。也就是依次输入5,空格,字符c。

运行结果:

Please input value:
5 c
5  

最后的打印只打印出了整数5,字符c没有打印出来。

接下来我们进行第二次输入:5 Enter

第二次的输入把空格换成回车键Enter,在依次输入5和回车后,程序就结束了。

运行结果:

Please input value:
5 
5 

第二次的运行结果多打印了一个空行。

现在我们对两次的运行结果进行分析。先从输入结果来看,也就是两次运行结果的第2行。scanf()在用户输入数据后,会在屏幕上回显输入值,所以当我们键入5后,屏幕回显了5。接下来就是两次输入结果发生差异的地方。在第一次输入中,键入5后是按下了空格,屏幕在回显了空格后还在等待第二次输入,也就是还可以输入字符c。但是第二次输入中,键入5后按下了回车,程序不再等待用户输入,直接运行结束。

然后再观察两次输入的打印结果,也就是两次运行结果的第3行。会明显的发现,虽然两次结果都只打印了整型值,但是第二次的打印结果还多打印了一个空行。这是为啥?接下来进行原理分析。

3.原理分析

第一次调用scanf("%d", …)时,stdin中没有数据,所以scanf()阻塞等待。当用户键入5后,在5后面又继续输入了空白符(空格或回车),scanf("%d", …)认为格式串中第一个格式对应的输入已经完成,5就是第一个输入的数据,然后读取5并与格式化字符串的第一个格式%d进行匹配,5符合%d所以scanf()将5写入到对应地址处,因为第一个scanf("%d", …)只需要接收一个参数,所以第一个scanf("%d", …)此时已经调用结束了。接下来轮到第二个scanf("%c", …)在stdin中读取数据。注意,在上次scanf("%d", …)结束后,stdin中是遗留了空白符(空格符或换行符)的,对,就是为了告诉scanf("%d", …)第一个格式输入结束而使用的空格(Space)或回车(Enter),所以此时stdin中是有数据的,而且是空格符或换行符的字符型数据。第二个scanf("%c", …)发现stdin中有数据,自然要直接进行读取,不再阻塞等待。因为它请求的输入格式是%c,stdin中遗留的空格或换行符恰好符合格式,所以第二次scanf()就把遗留的字符直接进行读取。

那为什么第二次scanf()读取到空格后还会回显下一次输入,而读取到换行则不会回显下一次的输入?

首先知道scanf("%c", …)与scanf(" %c", …)的不同,前者不会跳过空白字符,而后者会跳过所有空白字符(包括空格和回车换行)。后者后面再细说,现在只说前者。第一次scanf()结束后,空格遗留在stdin中,第二次scanf("%c")不会跳过空白符(空格或回车都不会放过,都被当作可读取的字符),所以会直接读取遗留的空格。另外,空格符还会让scanf()继续等待下一次输入(’\n’不会有这个效果,这里都是针对格式串为%c来说),且scanf("%c")与scanf(" %c")不同,scanf("%c")不是直到下一个非空字符才结束,而是直到下一个非空格字符才结束。也就是当你在空格符后按下回车后,它也会结束,而且此时屏幕没有回显程序就结束了(因为换行’\n’是空白符所以不会回显)。

依然是上面的程序,现在输入5 Space Enter,看结果:

Please input value:
5 
5  

此时的运行结果在空格后输入Enter,程序就结束了。

所以第二次scanf()读取到空格后会回显下一次输入,而读取到换行则不会回显下一次输入的原因是:空格和回车换行虽然都会被当作第一个字符参数读取,但是对于scanf("%c"),’\n’并不会让scanf()继续等待下一次输入,scanf()读取了’\n’就结束了。但是空格不同,scanf("%c")在读取了空格后,空格会让scanf()继续等待下一次输入,直到下一次的输入为非空格字符为止(上面的运行结果证明了这一点),注意是非空格而不是非空白字符,只有scanf(" %c")才会让scanf()等待下一次输入直到是非空白字符为止。

但是注意,如果第二个scanf()的参数不是字符,是%d,那么只要是空白符(无论空格或回车)都会让scanf()继续等待下一个输入。

另外,在输入数字5后再按空格与直接在输入格式后多加一个空格scanf("%d ", ...)这种写法的效果是有区别的,前者scanf("%d", …)在读取到5后就结束了,而后者scanf("%d “, …)在读取数字后,会继续读取下一个,并丢弃所有空白,直到在输入中看到非空白字符为止,并且该非空白字符将保留为输入函数要读取的下一个值。也就是此时scanf(”%d ", …)还不会结束,直到接收到了一个非空白字符时才结束,这个非空白字符会留在stdin缓冲区中,留给下一个函数进行读取。

现在改动上面的程序,把第二次scanf("%c", …)改为scanf("%c ", …),并在其后加一个读取字符的getchar()函数,将遗留在stdin中的下一个字符读取出来。

#include <stdio.h>

int main()
{
	int a;
	char c;
	char d;

	puts("Please input value:");
	scanf("%d", &a);
	scanf("%c ", &c);	/* 格式串后多加一个空格 */
	
	d = getchar();/* 读取遗留在stdin缓冲区的字符 */

	printf("%d %c %c\n", a, c, d);

	return 0;
}

运行结果:依次输入5 Space 字符c

Please input value:
5 c
5   c

从这次的运行结果可以看出,第一次scanf("%d", …)读取了5后结束,5后的空格留在了stdin中。第二次scanf("%c “, …)从stdin中直接读取了空格,然后继续等待输入,直到遇到非空白字符,此处是字符c,然后第二次scanf(”%c ", …)结束,字符c遗留在了stdin缓冲区中。接着用getchar()可以将字符c读取出来,运行结果与分析一致。

4.解决方式

错误的解决方式:

在两次scanf()中间添加fflush(stdin)。

这种方式是一种未定义的行为,也就是C标准并没有定义这样做会产生什么样的结果。另外,在gcc中这样做是没有效果的,可能使用别的编译器会有效,不建议这种方式。

正确的解决方式:

(1)使用getchar()吸收掉遗留在stdin中多余的字符。

#include <stdio.h>

int main()
{
	int a;
	char c;

	puts("Please input value:");
	scanf("%d", &a);
    getchar();	/* 使用getchar()吸收掉多余的字符 */
	scanf("%c", &c);

	printf("%d %c\n", a, c);

	return 0;
}

(2)在第二次scanf()的格式串前面多加一个空格。

#include <stdio.h>

int main()
{
	int a;
	char c;

	puts("Please input value:");
	scanf("%d", &a);
	scanf(" %c", &c);	/* 在格式串%c前面多加一个空格 */

	printf("%d %c\n", a, c);

	return 0;
}

%c前面多加一个空格会让scanf()显式地跳过所有空白符(包括空格和回车),直到接收到一个非空白字符。这样就可以让遗留在stdin中的空格符被忽略掉,不会被当作第一个字符参数被读取。

双重嵌套的 `scanf` 函数需要按回车键两次才能确定的原因是由于输入缓冲区的影响。 当你在终端中使用 `scanf` 函数输入数据时,数据会被存储在输入缓冲区中。当你按下回车键时,`scanf` 函数会从输入缓冲区中读取数据并进行处理。 在双重嵌套的 `scanf` 循环中,第一个 `scanf` 函数会读取输入缓冲区中的数据,然后将其存储到指定的变量中。但是,换行符(回车键)会被留在输入缓冲区中。 因此,在第二个 `scanf` 函数执行之前,它会读取输入缓冲区中剩余的换行符。只有当你第二次按下回车键时,第二个 `scanf` 函数才能从输入缓冲区中读取到有效的输入数据。 通过按回车键两次,你确保了第一个 `scanf` 函数读取了期望的输入,并且第二个 `scanf` 函数能够从输入缓冲区中正确读取到数据。 如果你想避免这种情况,可以在每个 `scanf` 调用之后使用额外的代码来清空输入缓冲区。你可以使用以下方式在每个 `scanf` 调用之后清空输入缓冲区: ```c #include <stdio.h> int main() { int a, b; scanf("%d", &a); while (getchar() != '\n'); // 清空输入缓冲区 scanf("%d", &b); while (getchar() != '\n'); // 清空输入缓冲区 // 继续处理 a 和 b 的值 return 0; } ``` 这样,你就可以确保每次输入后都能够正常读取到数据,而无需多次按下回车键。 希望这个解答对你有帮助。如果你还有其他问题,请随时提问。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值