垃圾代码评析——关于《C程序设计伴侣》6.2(二)
前文链接:http://www.cnblogs.com/pmer/archive/2012/11/04/2754294.html
【样本】
用qsort()函数对二维数组进行排序
1. #include <stdio.h> 2. #include <stdlib.h> 3. #include <string.h> 4. #include <math.h> 5. // 获得一维数组的平均值 6. float getaver(const int* st) 7. { 8. int count = 0; 9. int total = 0; 10. // 只要没有遇到表示数据结束的0值, 11. // 就一直累加所有数据的值 12. while(0!=*st) 13. { 14. ++count; // 数据个数加1 15. total += *st; // 累加数据 16. ++st; // 指向下一个数据 17. } 18. 19. // 返回平均值 20. if( 0 == count ) // 对没有元素的情况作特殊处理 21. { 22. return 0.0f; 23. } 24. else 25. { 26. return (float)total/count; 27. } 28. 29. } 30. 31. // 通过数组的平均值大小进行比较 32. int cmp(const void* a, const void* b) 33. { 34. // 获得两个数组的平均值 35. float avera = getaver((int*)a); 36. float averb = getaver((int*)b); 37. 38. // 返回比较结果 39. if(fabs(avera - averb) < 0.0001) 40. { 41. return 0; 42. } 43. else if( avera > averb ) 44. { 45. return 1; 46. } 47. else 48. { 49. return -1; 50. } 51. 52. } 53. int main() 54. { 55. // … 56. 57. // 利用qsort()函数对二维数组进行排序 58. qsort(scores, // 数组名,也就是数组首地址 59. classnum, // 数组元素个数 60. stnum*sizeof(int), // 每个数据元素占用的空间 61. cmp); // 比较规则函数 62. 63. return 0; 64. }
因为我们需要根据数组的平均值进行排序,所以我们首先定义了一个getaver()函数用于获取一个数组(以这个数组的首地址作为参数)的平均值。在getaver()函数中,我们利用一个while循环统计各个数据的总和以及数据个数,直到遇到表示数组数据结束的0为止(while(0!=*st))。最后getaver()返回的就是这个数组的平均值((float)total/count)。在比较规则函数cmp()函数中,我们正是利用getaver()函数来方便地夺得了两个待比较的数组的平均值,最后cmp()函数返回的是两个平均值相减的结果,也就是两个数组的比较结果。这里我们需要注意的是,cmp()函数的两个参数,在这里不再是指向两个单独的数据元素的指针,而成为了指向二维数组中的两个一维数组的指针,是这两个一维数组的首地址,所以可以经过类型转换后传递给getaver()函数直接获得两个数组的平均值。最后,qsort()函数会调用这个比较规则函数cmp()以此来实现二维数组的快速排序,而排序的结果也会重新写回这个数组。换句话说,经过qsort()函数调用后的数组,已经是排序完成的数组。
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p105~107
【评析】
这种头重脚轻的代码结构(把main()写在后面)非常难于阅读,使人不得不用“拿大鼎”的姿势来看代码。先来看main()。
这个函数中只写了一个函数调用,这个调用基本没有问题。只是其中的“stnum*sizeof(int)”是非常啰嗦的写法,是“sizeof scores”的画蛇添足版。
下面再来看cmp():
31. // 通过数组的平均值大小进行比较 32. int cmp(const void* a, const void* b) 33. { 34. // 获得两个数组的平均值 35. float avera = getaver((int*)a); 36. float averb = getaver((int*)b);
这里的a、b两个形参名我就不吐槽了。业余到什么程度大家心里有数。
要说的是35、36行,这里两次调用了getaver()函数,求两个一维数组所有元素的平均值,注意这里都只有一个实参,这个实参是指向一维数组起始元素的指针。通常这是不行的,因为getaver()函数并不清楚数组中元素的个数。
既然getaver()函数并不清楚数组中元素的个数,那它又如何求平均值呢?再来看一下getaver()函数。在这个函数中有这样一段代码
12. while(0!=*st) 13. { 14. ++count; // 数据个数加1
原来它试图通过找到0确定数组中有多少个数据是有效的。然而,前文分析表明,整个二维数组中可能一个0值元素都没有。在这种情况下,程序运行的结果必然是一场灾难。
仅此,就足矣判定这个程序死刑,MVP的代码是垃圾代码。除了做code review的反面教材,这种代码没有任何价值。
为了了解代码中的其他问题,不妨捂着鼻子把代码看完。
现在回到cmp(),其中
38. // 返回比较结果 39. if(fabs(avera - averb) < 0.0001) 40. { 41. return 0; 42. } 43. else if( avera > averb ) 44. { 45. return 1; 46. } 47. else 48. { 49. return -1; 50. }
其中的fabs(avera - averb) < 0.0001无疑是在判断avera、averb是否相等,但在这里其实有些煞有介事。写成
// 返回比较结果 if( avera < averb ) return -1; if( avera > averb ) return 1; return 0;
其实就很好。不但简洁,而且也不会产生其他什么问题。