关于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("%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) {}
至少我会写成这样。