内裤外穿——错位及不伦不类
内裤这种东西是穿在外裤里面的,穿在外面就成了一种“错位”。错位的结局必然是非驴非马、不伦不类。不要以为这种事情不会在代码中发生,代码中同样可能存在错位和不伦不类。
题目:用递归方法求n!。
#include <stdio.h>
int main()
{ int fac(int n);
int n;
int y;
printf("input an integer number:");
scanf("%d",&n);
y=fac(n);
printf("%d!=%d\n",n,y);
return0;
}
int fac(int n)
{
int f;
if(n<0)
printf("n<0,data error!");
elseif(n==0||n==1)
f=1;
else f=fac(n-1)*n;
return(f);
}
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p188
这段代码,当用户输入一个负整数后,main()函数会把这个错误的数据传给fac()函数,fac()函数会在输出设备上输出“n<0,data error!”,然而自相矛盾的是fac()函数还会把一个不确定的垃圾值错误地返回给main()函数,然而main()并不知道这是个错误的返回值,因而“很傻很天真”地输出这个毫无意义的返回值。最后的运行结果类似下面这样:
input an integer number:-1
n<0,data error!-1!=-1
一方面它说数据错误,但同时又告诉你“-1!=-1”。显然,这是一个不伦不类的结果。和内裤外裤同时暴露有得一拼。
然而,仅仅揭露这种荒谬是远远不够的,更重要的事情在于如何避免这种荒谬。而想避免这种荒谬就必须揭示产生这种荒谬的原因。
先考察一下样本代码的思路。main()函数主要由三个部分组成:输入n;计算n!;输出。而fac()函数的想法是如果n是负数,无法计算,输出“data error!”,否则如果n为0或1则f=1;否则计算“f=fac(n-1)*n”;最后返回f值。
问题就出在这里,不难发现fac()这个函数的功能几乎根本无法总结并描述,即使描述出来也是错误的,因为如果n是负数的话它最后同样也返回一个值。这种函数的功能是错乱的,造成这种错乱的原因在于,在构思main()的时候没有想到不是所有的整数都有阶乘(高等数学那么多存在性定理算是白学了),但是在写fac()又突然想到了,然而却不是回头重新考虑程序的总体思路,而是匆忙地把对负数的处理写在了fac()中,然而对负数的处理本应该在main()中进行,把这个处理写在fac()中就人为地制造出了一种“错位”,这就是结果不伦不类的根本原因。
造成错位的另一种可能性是,事先根本就没有总体的思路(main()),在main()写到一半时去写fac(),在写fac()想到了对负数的处理问题于是顺手写出,写完fac()之后再回到main()继续写完剩余部分的代码。这种缺乏总体构思东一榔头西一棒子写到哪算哪的写代码方法,完全违背了结构化程序设计自顶向下的思想,写出漏洞百出的程序是顺理成章的结局。
按照自顶向下的技术风格则不会产生这种问题。自顶向下要求首先构思main()函数:
int main( void ) { int n; //输入n //计算n! //输出n! return 0; }
训练有素的程序员应该能看出这个总体思路的逻辑毛病,因为计算n!的前提是n!存在且能够计算n!(n比较大时就完全成了另一个问题,这个问题这里不打算讨论。这里假设n不是很大,n!可用简单的办法求得)。历史上,人类曾花了2000多年的时间研究用尺规三等分角的方法,可最后才发现这种方法根本不存在,这个教训可谓深矣。如果不理解计算的前提是可以计算,不管学过多少数学,都算白学。
当然,无论是谁,思虑不周都是可能的。最初没想到而后来想到也不算是罪大恶极。但问题是后来一旦想到了就一定要返回main()重新审视总体构思,否则一旦顶层存在问题,代码再怎么写也是错的。俗话说,上梁不正下梁歪。人们往往能看到社会风气的败坏,但却很少思考败坏的源头何在,这样是解决不了问题的。所以,在这种情况下必须重新修正main()函数,才能继续后面的工作,而绝不能急着完成代码细节。
int main( void ) { int n; //输入n if ( n < 0 ) {//错误处理 } else { //计算n! // 输出n! } return 0; }
总体构思无误后,才有可能对fac()提出正确的功能要求。fac()只需要针对可计算的整数求阶乘并返回这个值,显然fac()的形参应该是unsigned类型,返回值也以unsigned类型为好,因为计算范围比返回值为int的更大些。把这些考虑用代码表达出来就是:
unsigned fac(unsigned); int main( void ) { int n; //输入n if ( n < 0 ) {//错误处理 } else { //计算n! // 输出n! } return 0; } //fac():计算n!并返回 unsigned fac(unsigned n) { }
到了这一步,后面的事情就是简单的力气活了。
#include <stdio.h> unsigned fac(unsigned); int main( void ) { int n; //输入n printf("请输入一个正整数:"); scanf("%d",&n); if ( n < 0 ) printf("该数小于0,无法计算!\n"); //错误处理 else printf("%d!=%u\n" , n , fac( (unsigned)n ) );//计算,输出n! return 0; } //fac():计算n!并返回 unsigned fac(unsigned n) { if ( n == 0U ) return 1U ; else return n * fac( n - 1U ) ; }
总结:
1.结构化程序设计的一个核心理念就是层次,自顶向下必须先建立层次这个概念才可能实现。
2.自顶向下就是由高层到低层,先粗后细,先大节后小节。切忌在各个不同的层次间玩“穿越”,否则就会导致层次错位,就如同把内裤套在外裤外面,结果必然不伦不类。
3.修改程序也必须遵循这样的次序,上层的问题切莫企图在下层修补;如果是上层存在问题,下层无论怎样忙活都无济于事。就如同老板弱智,员工勤恳一样,只会认认真真地把事情弄糟而已。