代码评析与重构——求完数问题
求完数问题
【题目2-10】
一个数如果恰好等于它的因子之和,这个数就称为"完数"。例如,6的因子为1、2、3,而6=1+2+3,因此6是“完数”。编程序找出1000之内的所有完数,并按下面格式输出其因子:
6 Its factors are 1 2 3
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p43
【评析2-10-1】
6的因子(divisor)有1、2、3和6共4个,所以所谓的完数(perfect number)显然绝非指那些“一个数如果恰好等于它的因子之和”的数。
在数论中,完数(perfect number)指的是一个正整数,它等于其正的真因数(proper positive divisors)之和。所谓正的真因数不包括该正整数自身。
【样本2-10】
1. #define M 1000 //定义寻找范围 2. #include <stdio.h> 3. int main() 4. { 5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10; 6. int i,a,n,s; 7. for(a=2;a<=M;a++) //a是2~1000之间的整数,检查它是否完数 8. {n=0; //n用来累计a的因子的个数 9. s=a; //s用来存放尚未求出的因子之和,开始时等于a 10. for(i=1;i<a;i++) //检查i是否a的因子 11. if(a%i==0) //如果i是a的因子 12. {n++; //n加1,表示新找到一个因子 13. s=s-i; //s减去已找到的因子,s的新值是尚未求出的因子之和 14. switch(n) //将找到的因子赋给k1~k9,或k10 15. {case 1: 16. k1=i;break; //找到的第1个因子赋给k1 17. case 2: 18. k2=i;break; //找到的第2个因子赋给k2 19. case 3: 20. k3=i;break; //找到的第3个因子赋给k3 21. case 4: 22. k4=i;break; //找到的第4个因子赋给k4 23. case 5: 24. k5=i;break; //找到的第5个因子赋给k5 25. case 6: 26. k6=i;break; //找到的第6个因子赋给k6 27. case 7: 28. k7=i;break; //找到的第7个因子赋给k7 29. case 8: 30. k8=i;break; //找到的第8个因子赋给k8 31. case 9: 32. k9=i;break; //找到的第9个因子赋给k9 33. case 10: 34. k10=i;break; //找到的第10个因子赋给k10 35. } 36. } 37. if(s==0) 38. { 39. printf("%d,Its factors are ",a); 40. if(n>1)printf("%d,%d",k1,k2); //n>1表示a至少有2个因子 41. if(n>2)printf(",%d",k3); //n>2表示a至少有3个因子 42. if(n>3)printf(",%d",k4); //n>3表示a至少有4个因子 43. if(n>4)printf(",%d",k5); //以下类似 44. if(n>5)printf(",%d",k6); 45. if(n>6)printf(",%d",k7); 46. if(n>7)printf(",%d",k8); 47. if(n>8)printf(",%d",k9); 48. if(n>9)printf(",%d",k10); 49. printf("\n"); 50. } 51. } 52. return 0; 53. }
【评析2-10-2】
这代码!丑的简直惊天地泣鬼神。
5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;
一上来就一口气定义了10个变量,颇有愚公移山的气概,吃奶的力气都使出来了。
可惜的是从名字上根本看不出这些变量是做什么用的。实际上这是一种恶劣的命名方法。从后面的代码来猜,这些变量应该是用于存放各个因子的。但是为什么要定义十个呢?毫无根据。
是1000之内的自然数的因子不多于十个吗?这是压根不成立的。譬如,210有1,2,3,5,6,7,10,14,15,21,30,35,42,70,105,210共16个因子,即使不考虑其自身,也有15个因子。定义10个变量存放因子显然是一种粗暴且武断的做法,因此代码无疑是错误的。如果它能输出正确的结果,那也只不过是瞎猫碰到死耗子的巧合而已。
6. int i,a,n,s;
这里最多需要定义一个变量a用于完成循环。一起定义了4个说明思维漂移太远,不清楚当下到底应该做什么。有句话叫活在当下,编写代码也是如此,不用急着定义当下根本用不着的变量。
7. for(a=2;a<=M;a++) //a是2~1000之间的整数,检查它是否完数 8. {n=0; //n用来累计a的因子的个数 9. s=a; //s用来存放尚未求出的因子之和,开始时等于a 10. for(i=1;i<a;i++) //检查i是否a的因子 11. if(a%i==0) //如果i是a的因子 12. {n++; //n加1,表示新找到一个因子 13. s=s-i; //s减去已找到的因子,s的新值是尚未求出的因子之和 14. switch(n) //将找到的因子赋给k1~k9,或k10 15. {case 1: 16. k1=i;break; //找到的第1个因子赋给k1 17. case 2: 18. k2=i;break; //找到的第2个因子赋给k2 19. case 3: 20. k3=i;break; //找到的第3个因子赋给k3 21. case 4: 22. k4=i;break; //找到的第4个因子赋给k4 23. case 5: 24. k5=i;break; //找到的第5个因子赋给k5 25. case 6: 26. k6=i;break; //找到的第6个因子赋给k6 27. case 7: 28. k7=i;break; //找到的第7个因子赋给k7 29. case 8: 30. k8=i;break; //找到的第8个因子赋给k8 31. case 9: 32. k9=i;break; //找到的第9个因子赋给k9 33. case 10: 34. k10=i;break; //找到的第10个因子赋给k10 35. } 36. } 37. if(s==0) 38. { 39. printf("%d,Its factors are ",a); 40. if(n>1)printf("%d,%d",k1,k2); //n>1表示a至少有2个因子 41. if(n>2)printf(",%d",k3); //n>2表示a至少有3个因子 42. if(n>3)printf(",%d",k4); //n>3表示a至少有4个因子 43. if(n>4)printf(",%d",k5); //以下类似 44. if(n>5)printf(",%d",k6); 45. if(n>6)printf(",%d",k7); 46. if(n>7)printf(",%d",k8); 47. if(n>8)printf(",%d",k9); 48. if(n>9)printf(",%d",k10); 49. printf("\n"); 50. } 51. }
这个实在令人无语。这恐怕是C语言有史以来最长的for语句,整整写了45行,绝对应该载入史册。尽管前辈编程达人曾经谆谆告诫过我们,Don’t be too clever(不要过于机灵),但是依本人粗陋的理解,这绝对不是让我们过于愚蠢。
9. s=a; //s用来存放尚未求出的因子之和,开始时等于a
从后面的代码来看,s根本不是如注释中所说的那样“用来存放尚未求出的因子之和”,因为谁也不知道,尚未求出的因子和是多少,但是s的初值却是a。所以,注释和代码中至少有一个是口是心非地在撒谎。
14.~35.行是错误的。原因前面提到过,1000以内的正整数的真因子可能多于10个。整整12行啊!悲催无比。程序员最大的悲剧是什么?就是不辞辛苦兢兢业业认认真真吭哧瘪肚地写出了一大堆繁复无比比烂棉花套还要复杂的代码之后,暮然回首,却被指出那代码完全是错误的。
至此,我们同样不难判断出37.~49.行同样是错误的。这很自然。程序的全部基础都建立在不靠谱的
5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;
基础之上。这个基础既然是荒谬的,整个代码必然是错误的,想改都没法改。只能将之完全推倒重来。
最后还有一个错误不容易被发现。这个程序的输出是:
6,Its factors are 1,2,3
28,Its factors are 1,2,4,7,14
496,Its factors are 1,2,4,8,16,31,62,124,248
这个输出与题目要求的格式:
6 Its factors are 1 2 3
并不一致。
【重构2-10】
1 #include <stdio.h> 2 3 #define TOP 1000 4 5 int main( void ) 6 { 7 int n ; 8 for( n = 1 ; n <= TOP ; n++) 9 { 10 int fac , sum ; 11 for( sum = 0 , fac = 1 ; fac < n ; fac++) //求真因子和 12 if( n % fac == 0 ) 13 sum += fac ; 14 15 if( sum == n ) //真因子和与整数相等 16 { 17 printf("%d是一个完全数,其真因子为:",n); 18 for( fac = 1 ; fac < n ; fac++) 19 if( n % fac == 0 ) 20 printf(" %d",fac); 21 22 putchar('\n'); 23 } 24 } 25 26 return 0; 27 }