谭浩强C第四版126页例5.4的瑕疵及随想
谭浩强C第四版126页例5.4的瑕疵及随想126页例5.4如下:
在全系1000学生中,征集慈善募捐,当总数达到10万元时就结束,统计此时捐款到人数,以及平均每人捐款的数目。
程序如下:
#include<stdio.h>
#define SUN 100000
int main( )
{
float amount, aver, total;
int i;
for(i=1,total=0; i<1000;i++)
{
printf("please enter amount:");
scanf("%f", &amount);
total=total+amount;
if(total>=SUN)break;
}
aver=total/i;
printf("num=%d\n aver=%10.2f\n", i, aver);
return 0;
}
当总数达到10万元时就结束,如果所有的人都捐款完毕但是总数仍没达到10万元还结束吗?当然结束了!这就是题意。
为了验证程序的正确性,我们必须把它改一下,因为循环1000次可不是闹着玩的,就改成循环3次吧。程序修改如下:
#include<stdio.h>
#define SUN 100000
int main( )
{
float amount, aver, total;
int i;
for(i=1,total=0; i<=3;i++)
{
printf("please enter amount:");
scanf("%f", &amount);
total=total+amount;
if(total>=SUN)break;
}
aver=total/i;
printf("num=%d\n aver=%10.2f\n", i, aver);
return 0;
}
我们运行它,程序出现3次提示:please enter amount:,我们分别输入1、2、3。显然,我们期望的结果是:
num=3
aver=2
然而,程序输出的结果是
num=4
aver=1.5
程序有错!
当然啦,您也可以认为我这样输入是不符合题目中的人情的,哪能所有的人都募捐以后,还没达到预定的指标啊?不合国情啊!如果您如此想,那是我错了。
如果您同意把人情去掉,我们可以把上面的题意进行抽象,或者说把这个干巴巴的程序进行最低等级的演绎,则此程序是完成下面的一项工作:
“输入不超过1000个浮点数,如中途累加和达到100000则停止,计算输入的浮点数个数极其平均值。”
倘若您同意后者,那么谭老师的这个程序有错。
怎样使得程序避免这个问题呢?我有个方法如下:
#include<stdio.h>
#define SUN 100000
int main( )
{
float amount, aver, total;
int i;
for(i=1,total=0; i<=3;i++)
{
printf("please enter amount:");
scanf("%f", &amount);
total=total+amount;
if(total>=SUN)break;
}
if(i==4)
i--;
aver=total/i;
printf("num=%d\n aver=%10.2f\n", i, aver);
return 0;
}
但是这个方法并不好。
首先让我们分析一下错误发生的原因。我们看到,for循环有两个出口,一个正常出口和一个非正常出口break。变量i的值与这两个出口密切相关。
先看正常出口,正常出口实际上是for循环的第二个表达式(当然它也是入口),这里是i<=3,只有i是4时才会退出循环。此时i的值比实际输入的浮点数的个数多1。
非正常循环则不然,它退循环时没有被多加上1!此时i与输入的浮点数的个数是一致的。
现在您明白我为什么要加上:
if(i==4)
i--;
它的意思是说,如果是正常出口,i的值要减1。
那么为什么这个改法并不好呢。这要先搞清楚错误的最终根源出在哪里。如果您把这个错误单纯看成不小心或者对for循环和break的理解上不准确还是不够的。现在让我们回顾一下软件工程的一些常识。
软件工程十分强调一个函数或者一个变量只实现一个功能或者代表一个意义。您看我们的变量i,它现在既是循环次数的控制器又是输入浮点数个数的计数器,一身兼二职,且二职相互干扰。
软件工程十分强调程序的可读性,因此我们建议写循环时要使得循环变量一目了然,越简单越好,最好是一种简单且规范的形式:
for(i=0; i<10; i++)
您看在原程序这里塞上了total=0,我窃以为有两点不好。一是使得for循环不够简单清晰;二是不符合软件工程里尽可能初始化的原则。
另外,软件工程强调良好的程序书写风格,您看这样写就不太好:
if(total>=SUN)break;
而写作下列形式可读性就要好些:
if(total>=SUN)
break;
鉴于以上的讨论,笔者编程序如下,望朋友们指正:
#include<stdio.h>
#define SUN 100000
int main( )
{
float amount, aver, total=0;
int i, num=0;
for(i=1; i<=3;i++)
{
printf("please enter amount:");
scanf("%f", &amount);
total=total+amount;
num++;
if(total>=SUN)
break;
}
aver=total/num;
printf("num=%d\n aver=%10.2f\n", num, aver);
return 0;
}
在上面的程序里,变量num专作输入的浮点数个数的计数器,而i专作循环的控制器,同时遵循了尽可能初始化的原则。