帮初学者改代码——有多少青春可以挥霍之“c语言 多重排序”

原文:“c语言 多重排序

原代码:

  原代码格式很乱,可能是作者初学,也可能是因为不懂得如何排版,这里重新排了一下。

  首先, 

1
2
3
4
5
struct A
{
   char name[100];
   int gread;
} stu[100];

  这明显是谭氏代码的垃圾写法,在谭书中到处都有这种垃圾写法。特点是不假思索地顺手就定义外部变量,顷刻间就把代码的结构毁于一旦。

  这个外部变量非但毁了代码的结构,而且废了fact()函数。使得fact()函数只能用于这个特定的外部数组。这种不具备一定通用性的函数是不合格的函数。

  这种写法完全是为了图省事,在声明结构体类型的同时直接定义变量,以后就不用再写一遍雍长的结构体类型的名字了。初学者很难理解这种图省事的方式有什么不好,因为感觉这很方便。这就是谭书给初学者带来的隐蔽毒害,让初学者很开心地掉入谭书挖的大坑。

  实际上这种方便就如同把卫生间里用的手纸不但放在了卫生间,同时也放到客厅、餐厅、卧室和厨房一样,无论在客厅还是在餐厅、卧室和厨房只要有了便意,立刻就可以拿到手纸。会不会也有人觉得也很方便呢?

  并不是绝对不能使用外部变量,就如同并非绝对不能使用goto语句一样。但高手对使用这种可能伤人又害己的“七伤拳”总是慎之又慎的。只有在迫不得已、有充分理由的前提下才动用这种武器。

  滥用外部变量就如同把程序的代码都写在一个main()中一样,是初学者常见的幼稚病。而从程序效率方面来说,滥用外部变量还不如把所有代码都写在main()当中。

  一般来说,变量应该定义在局部,只要可能,越局部越好。对于目前这个问题而言,stu数组应该定义在main()之中。

  此外,struct的tag——A,这名字实在是太过随意了,也是谭氏风格。

1
int n,i;

  这个 i 压根不应该出现在main()中。

1
scanf("%d",&n);

  这种赤裸裸的scanf()也是初学者代码中常见的毛病。写成下面样子比较好:

puts("请输入人数");
scanf("%d",&n);

 

1
2
3
4
5
for(i=1;i<=n;i++) 
{
   scanf("%s",&stu[i].name);
   scanf("%d",&stu[i].gread);
}

  这段问题较多。

  首先,数组下标应该是从0开始的,但这里数组的下标却是从1开始的;

  其次专业程序员的for语句一般这样写

   for( i = 0 ; i < n ; i++ )

   第三,这段代码写成一个函数为好。 

1
fact(n);

  感觉这个函数的功能是排序,但名字居然是“fact”,实在令人莫名其妙。前面结构体的成员名gread同样令人困惑不解。英文不好,可以用汉语拼音,不丢人。一些四不像的英语才丢人。但是说起来很奇怪,有不少假洋鬼子反对用汉语拼音做标识符,但他们对那种蹩脚的、错误连篇的英文(比如老谭书中的)却从来一声不吭。那是一群很奇怪的动物,他们反对使用国家法定的母语拼音,但不反对使用垃圾甚至错误的非母语。

  这个函数的参数也不完整。它是要对数组排序,却没有数组名这个实参。这也是外部变量惹的祸。这样的函数没有什么实用价值,一点可复用性都谈不上。如果问题中另有一个数组需要排序,难道再写一个函数不成?

  由于这个函数要完成所谓的“多重排序”,所以它的比较规则要更加复杂。因此还应该有第三个参数——一个用于比较的函数作为参数。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void fact(int n)
   int i,j,teap;
   char w[100];   
   for(i=1;i<=n;i++)   
      for(j=2;j<=n;j++) 
      {  
         if(stu[j-1].gread>stu[j].gread)    
            teap=stu[j-1].gread,stu[j-1].gread=stu[j].gread,stu[j].gread=teap;
         else
         {     
            if(strcmp(stu[j-1].name,stu[j].name)>0)
            strcpy(w,stu[j-1].name),strcpy(stu[j-1].name,stu[j].name),strcpy(stu[j].name,w);
         }
      }
}

  这个函数定义被写在了调用之前,从而没有写函数类型声明。这也是一种偷懒的写法,贪小便宜。无论把函数定义写在哪里都应该写函数类型声明。

1
int i,j,teap;

  teap这个标识符也很诡异,从来没见过这个单词。 

1
for(i=1;i<=n;i++)

  这个属于一错再错,结果是负负得正,歪打正着。

1
2
teap=stu[j-1].gread,stu[j-1].gread=stu[j].gread,stu[j].gread=teap;
strcpy(w,stu[j-1].name),strcpy(stu[j-1].name,stu[j].name),strcpy(stu[j].name,w);

  这种写法很垃圾,是对逗号运算的滥用。它除了使代码变模糊之外,没有任何好处。不但害人,而且害己。因为不但别人难以看懂,自己也难以看明白。一旦这里有错,哭都来不及。

  此外这应该用函数完成,而不是在当地写。 

1
2
3
4
5
6
7
if(stu[j-1].gread>stu[j].gread)    
   teap=stu[j-1].gread,stu[j-1].gread=stu[j].gread,stu[j].gread=teap;
else
{     
   if(strcmp(stu[j-1].name,stu[j].name)>0)
   strcpy(w,stu[j-1].name),strcpy(stu[j-1].name,stu[j].name),strcpy(stu[j].name,w);
}

  这个逻辑是错误的。因为按照第二个指标比较的前提是第一个指标相同。而else部分则包括了前一个元素的第一指标小于第二个元素的第一指标的情形(即stu[j-1].gread<=stu[j].gread的情况)。

  另一个让人感觉诡异的是,这里的代码只交换了结构体变量的成员,而不是交换整个结构体变量。不知道这表达的是什么逻辑。

1
for(j=2;j<=n;j++) 

  感觉是在写冒泡法排序,但这个“j<=n”表明冒泡法还没学懂。

  最后,main()中的

1
2
for(i=1;i<=n;i++)   
   printf("%s\n",stu[i].name);   

  错误同前。这里只输出name成员也不对,这根本无法测试程序是否完成了功能。此外这段也应该写成一个函数。

  下面是重构的代码。

复制代码
#include<stdio.h>
#include<string.h>

struct xuesheng
{
   char xingming[100] ;
   int  chengji ;
} ;

void shuru( struct xuesheng [] , int );
void shuchu( struct xuesheng [] , int );
void paixu( struct xuesheng [] , int , 
            int (*)( struct xuesheng , struct xuesheng ) ); 
void jiaohuan( struct xuesheng * , struct xuesheng * );
int dayu( struct xuesheng , struct xuesheng );

int main( void )
{
   int renshu ;
   struct xuesheng ban[100] ; 
   
   puts("请输入人数");
   scanf("%d",& renshu );  
   
   shuru( ban , renshu );
   
   paixu( ban , renshu , dayu ); 
   
   shuchu( ban , renshu );
    
   return 0;
}

int dayu( struct xuesheng xs1 , struct xuesheng xs2 )
{
   if ( xs1.chengji > xs2.chengji )
   {
      return 1;
   }
   
   if ( xs1.chengji < xs2.chengji )
   {
      return -1;
   }
      
   if ( xs1.chengji == xs2.chengji )
   {
      return strcmp(xs1.xingming ,xs2.xingming );
   }
      
}

void jiaohuan(struct xuesheng * p1 , struct xuesheng * p2 )
{
   struct xuesheng tmp = * p1 ;
   * p1 = * p2 ;
   * p2 = tmp ; 
}

void paixu(  struct xuesheng b[] , int rs  , int (*dy)( struct xuesheng , struct xuesheng ) )
{
   int i ;
   
   for ( i = 1 ; i < rs ; i ++ )
   {
      int j ;
      for ( j = 0 ; j < rs - i ; j ++  )
      {
         if ( dy( b[ j ] , b[ j + 1 ] ) == 1 )
         {
            jiaohuan( &b[j] , &b[j+1] );
         }
      }
   }
}

void shuchu( struct xuesheng b[] , int rs )
{
   int i ;
   for ( i = 0 ;i < rs ; i++ )  
   { 
      printf("%s ", b[i].xingming);
      printf("%d \n" ,b[i].chengji);
   }       
}

void shuru ( struct xuesheng b[] , int rs )
{
   int i ;
   for ( i = 0 ;i < rs ; i++ )  
   { 
      puts("姓名?");
      scanf("%s",b[i].xingming);
      puts("成绩?");         
      scanf("%d",&b[i].chengji);
   }       
}
复制代码

 

posted @   garbageMan  阅读(1855)  评论(16编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2012-11-06 垃圾代码评析——关于《C程序设计伴侣》6.2(三)
2010-11-06 Byte是8位吗?
点击右上角即可分享
微信分享提示