关于while的循环条件检测scanf返回值的分析和推荐设置

起因

看到下面这样一段代码:

#include <stdio.h>
int main()
{
	int n, sum = 0;
	while (scanf("%d", &n) != -1) {
		for (int i = 0; i <= n; i++) {
			sum = sum + i;
		}
		printf("%d\n\n", sum);
		sum = 0;
	}
	return 0;
}

代码的来源是什么HDOJ 1001问题的解答。在网上搜索了一下,是一个程序题,题目如下:

Problem Description
Hey, welcome to HDOJ(Hangzhou Dianzi University Online Judge).
In this problem, your task is to calculate SUM(n) = 1 + 2 + 3 + ... + n.


Input
The input will consist of a series of integers n, one integer per line.


Output
For each case, output SUM(n) in one line, followed by a blank line. 
NOTE:You may assume the result will be in the range of 32-bit signed integer.


Sample Input

1
100

Sample Output

1

5050

看了一圈网上贴出来的解答代码,基本上都用while循环,循环条件是scanf的返回值不等于EOF:

while (scanf("%d", &n) != -1) {}

而且,还有同学特别发帖提醒自己:

注意: 一定要写while ( ) ! = EOF或者! = - 1

感觉是不是稍微有一点点过了? 为什么呢?
因为scanf的返回值实际上是有三种大的情况的呀!

下面开始捋一捋scanf函数的返回值。

scanf 的返回值是它成功获得对象的个数,当在接收到第一个对象之前遇到了类型匹配错误的话,返回值是0,而如果在接收到第一个对象之前遇到了输入错误的话则返回值是EOF。

所以,总结起来,

scanf 的返回值有三种大的情况:

  • (1)成功接收到的对象的个数,不一定正好就等于格式符的个数,这取决于实际输入;
  • (2)0;
  • (3)EOF。

下面两张截图是从《C Reference》里截取的,读取文档的软件名称是Zeal,开源软件,github上有。
关于while的循环条件检测scanf返回值的分析和推荐设置关于while的循环条件检测scanf返回值的分析和推荐设置

输入值与返回值的情况分析

所以,上面这段循环代码里,

while (scanf("%d", &n) != -1) {}
// 或者,
// while (scanf("%d", &n) != EOF) {}

因为scanf 里的格式符是 %d ,如果有一个整型数据被它接收到的话,那么 返回值就肯定是 1 了, 但是如果这时候从键盘上输入一个不是数字的字符,比如字母或者别的符号的话,那么返回值就是 0 了。

因为scanf没有接收到匹配 %d 格式的数据。而本来期待接收到一个整数的变量 n 因为没有被初始化,或者现在很多编译器会默认将它初始化为 0 了, 那么 n 的值 就是 不确定的或者是 0 ,那么 for 循环的执行次数也就可能是不确定的 。

这还不是关键。

关键是,

scanf 没有得到类型相匹配的数据,输入缓冲区的那个 字母 就还在输入缓冲区里 等着被读取, 到了下一次的 while 循环的时候, scanf 又 读到了 第一次输入的那个 字母, 返回值又是 0 , 又进入循环。

所以,这样就会出现一个 死循环 !

当然了,你也许会问,那么不输入字母不就好了吗?

是的,不输入字母就不会出现死循环,只是说万一不小心按错了,或者不小心数字后面接了一个字母或者别的字符呢?毕竟键盘上的数字键旁边有的是字母或符号键。

改进方案

或者说,就算这种概率相当相当小吧,那么换个角度看,如果把循环条件改成下面这样,如何?

while (scanf("%d", &n) == 1) {}

意思是,只有scanf 接收到一个类型匹配的数据的时候才进入循环体内, 而要结束while循环的时候,随便输入一个不匹配 %d 类型的数据,比如一个字母或者一个别的符号,再回车即可。

甚至还可以在最后一次循环的时候,直接输入「一个数字+字母或别的符号」的组合,然后数字的计算结果也照常出来了,循环也顺势结束了。这样,会不会稍微轻松或者灵活一点点呢?

另外,也不妨碍直接手动输入EOF来结束循环,Linux下实测按下Ctrl+D后,循环结束。

总而言之,这样做的话,无论什么样的输入都不会出现死循环的情况了,而且不需要额外的代码来判断输入的数据是否合适。

不知道,

while (scanf("%d", &n) != -1) {}
// 或者,
// while (scanf("%d", &n) != EOF) {}

是从哪里流传或者流行起来的,但通过上面的分析和测试,还是推荐写成下面这样

推荐

while (scanf("%d", &n) == 1) {}

至少我会写成这样。

全文结束