劣质代码评析——兼谈指针越界问题

【题目】
  15.有一个班4个学生,5门课程:①要求计算第一门课程的平均分;②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;③找出平均成绩在90分以上或全部成绩在85分以上的学生。分别编写3个函数实现以上3个要求。
    ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p117
【评析】
  题目前提基本充分,要求大体也还算合理,除了“输出他们的学号”让人感到有些突兀,因为前面从来没提到“学号”,看来是打算用4个学号表示“4个学生”。但是“分别编写3个函数实现以上3个要求”这种要求,则属于僭越无理,这种要求是一种误导和教唆,在这种荒唐变态的要求下代码必然扭曲。
【原代码】

View Code
 1 #include <stdio.h>                               
 2 int main()
 3 {void avsco(float *,float *);
 4 void avscour1(char (*)[10],float *);
 5 void fali2(char course[5][10],int num[],float *pscore,float aver[4]);
 6 void good(char course[5][10],int num[4],float *pscore,float aver[4]);
 7 int i,j,*pnum,num[4];
 8 float score[4][5],aver[4],*pscore,*paver;
 9 char course[5][10],(*pcourse)[10];
10 printf("input course\n");
11 pcourse=course;
12 for(i=0;i<5;i++)
13    scanf("%s",course[i]);
14 printf("input NO. and scores:\n");
15 printf("NO.");
16 for(i=0;i<5;i++)
17    printf(",%s",course[i]);
18 printf("\n");
19 pscore=&score[0][0];
20 pnum=&num[0];
21 for(i=0;i<4;i++)
22    {scanf("%d",pnum+i);
23     for(j=0;j<5;j++)
24       scanf("%f",pscore+5*i+j);
25    }
26 paver=&aver[0];
27 printf("\n\n");
28 avsco(pscore,paver);
29 avscour1(pcourse,pscore);  
30 printf("\n\n");
31 fali2(pcourse,pnum,pscore,paver);  
32 printf("\n\n");
33 good(pcourse,pnum,pscore,paver);  
34 return 0;
35 }
36 
37 void avsco(float *pscore,float *paver)
38 {int i,j;
39 float sum,average;
40 for(i=0;i<4;i++)
41   {sum=0.0;
42    for(j=0;j<5;j++)
43     sum=sum+(*(pscore+5*i+j));
44    average=sum/5;
45    *(paver+i)=average;  
46   }
47 }
48 
49 void avscour1(char (*pcourse)[10],float *pscore)
50 {int i;
51 float sum,average1;
52 sum=0.0;
53 for(i=0;i<4;i++)
54    sum=sum+(*(pscore+5*i));
55 average1=sum/4;
56 printf("course 1:%s average score:%7.2f\n",*pcourse,average1);
57 }
58 
59 void fali2(char course[5][10],int num[],float *pscore,float aver[4])
60 {int i,j,k,label;
61 printf("  ==========Student who is fail in two courses=======\n");
62 printf("NO.");
63 for(i=0;i<5;i++)
64    printf("%11s",course[i]);
65 printf("    average\n");  
66 for(i=0;i<4;i++)
67 {label=0;
68   for(j=0;j<5;j++)
69    if(*(pscore+5*i+j)<60.0)label++;
70   if(label>=2)
71    {printf("%d",num[i]);
72     for(k=0;k<5;k++)
73       printf("%11.2f",*(pscore+5*i+k));
74     printf("%11.2f\n",aver[i]);  
75    } 
76 }   
77 }
78 
79 void good(char course[5][10],int num[4],float *pscore,float aver[4])
80    {int i,j,k,n;
81   printf("  ==========Students whose score is good=======\n");
82   printf("NO.");
83   for(i=0;i<5;i++)
84     printf("%11s",course[i]);
85   printf("    average\n");
86   for(i=0;i<4;i++)
87    {n=0;
88     for(j=0;j<5;j++)
89       if(*(pscore+5*i+j)>85.0)n++;
90     if(n==5||(aver[i]>=90))
91     {printf("%d",num[i]);
92      for(k=0;k<5;k++)
93        printf("%11.2f",*(pscore+5*i+k));
94      printf("%11.2f\n",aver[i]);  
95     } 
96 }   
97 }

    ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p117~120
  下面逐条评析。
【原代码】

02. int main()

【评析】
  这是一个老生常谈的问题,定义函数而不写形参类型亦即非函数原型形式(not prototype-format)是一种过时的写法(an obsolescent feature)。main()函数的头部应该写为:

int main ( void )

 【原代码】

03.{void avsco(float *,float *);
04.void avscour1(char (*)[10],float *);
05.void fali2(char course[5][10],int num[],float *pscore,float aver[4]);
06.void good(char course[5][10],int num[4],float *pscore,float aver[4]);

【评析】
  这个也是谭氏代码特有的一个毛病。把函数类型声明写在函数之内,不但使函数臃肿不堪,而且限制了函数的使用范围。应该从函数中移出,放在main()之前为好。
【原代码】

07.int i,j,*pnum,num[4];
08.float score[4][5],aver[4],*pscore,*paver;

【评析】
  int num[4];和float score[4][5];是这段代码核心的数据结构,如果不考虑使用结构体数据类型的话,尚属合理。但把i,j,pnum和num放在一起定义,尤其是把i,j和num放在一起声明,则显得不伦不类。
  把score数组元素设计为float类型并非不可,但矛盾的是,后面出现了
【原代码】

69.   if(*(pscore+5*i+j)<60.0)label++;
89.   if(*(pscore+5*i+j)>85.0)n++;
90.    if(n==5||(aver[i]>=90))

【评析】
  前两句中的表达式中有float与double的混合运算,后一句中出现了float与int的混合运算,这在一般情况下是应该竭力避免的。因为这可能会导致截断误差的警告,而且至少在理论上存在着不必要的类型转换导致程序低效。
  至于“aver[i]>=90”,通常是对类型不清醒而写出的似是而非的表达式,正规的写法应该是:aver[i]>=90.0F。
【原代码】

10.printf("input course\n");
11.pcourse=course;
12.for(i=0;i<5;i++)
13.   scanf("%s",course[i]);

【评析】
  这几句话的作用是输入5门课程名称,但却是画蛇添足。因为题目并没有要求输入课程名称,所以课程名称应该视为已知。
  不满足题目要求是一种错误,完成题目额外要求也是一种错误,这叫做多做之过。
  即使是出于纯粹个人练习的目的故意多写一段不必要的代码,这段代码也应该写成一个函数。
  其中最可笑的是11.行那条语句,因为它和这段代码的功能毫无关系,不当不正地插在这里显得非常不伦不类。后面可以看到这条语句不但位置是错误的,而且压根就是一句可以删除的废话。
【原代码】

14.printf("input NO. and scores:\n");
15.printf("NO.");
16.for(i=0;i<5;i++)
17.   printf(",%s",course[i]);
18.printf("\n");

【评析】
  这段代码的作用是输出一段提示信息,并为输入提供一个表头。但是",%s"这个格式设计得很拙劣,它的输出的效果是:
NO.,English,Computer,Math,Physics,Chemistry
  NO.与English之间的“,”不但不伦不类,而且这种样式容易让程序的使用者误认为输入数据之间应该用“,”分隔。
  实际上这段代码没有必要,如果需要写也应该用函数完成。
【原代码】

19.pscore=&score[0][0];
20.pnum=&num[0];

【评析】
  这是两句无厘头的废话。
  首先它们完全可以写成更简洁的形式:

pscore=score[0];
pnum=num;

   其次,这两句话完全可以不写。也就是说 pscore 和 pnum 这两个变量完全是多余的。
【原代码】

21.for(i=0;i<4;i++)
22.   {scanf("%d",pnum+i);
23.    for(j=0;j<5;j++)
24.      scanf("%f",pscore+5*i+j);
25.   }

【评析】
  这段代码中的
  scanf("%d",pnum+i);
一句应该写成

scanf("%d",num+i);

  而
  scanf("%f",pscore+5*i+j);
则等价于

scanf("%f", *score + 5 * i + j );

  但是需要特别指出的是,这两者都是错误的写法。这个错误就是本文副题所提到的指针越界问题
  指针可以与整数做加、减运算是有前提的。前提之一是这个指针必须是指向数据对象(Object)。例如:

int i;

   &i这个指针可以+0、+1。但是指向函数的指针或指向void类型的指针没有加减法运算。

  前提之二是这个指针必须指向数组元素(单个Object视同一个元素的数组)或指向数组最后一个元素之后的那个位置。例如:

int a[2];

   &a[0]、&a[1]、&a[1]+1(即a、a+1、a+2)这些指针可以进行加减法运算。
  第三,指针进行加减法运算的结果必须也指向数组元素或指向数组最后一个元素之后的那个位置。例如,对于指向a[0]的指针a,只能+0、+1、+2,对于a+2这个指针,只能-0、-1、-2。如果运算结果不是指向数组元素或指向数组元素最后一个元素之后的位置的情况,C语言并没有规定这种运算行为的结果是什么,换句话说这是一种未定义行为(Undefined Behavior,后面简称UB)。
  前面【原代码】中的pscore是一个float *类型的指针,它指向的是
  float score[4][5];
中的score[0][0],score[0][0]是score[0]这个一维数组(float [5])的元素,因此pscore只可以+0、+1、+2、+3、+4、+5,但是绝对不能加其他的整数。而
  pscore+5*i+j
在i≥1,j≥0时显然违背了这种规则。因此是一种未定义行为,亦即是一种错误的代码。
  很多人以为,既然
  pscore+5*i+j
没有超出二维数组的范围,因此就没有问题。这种看法是错误的。构成二维数组的元素并非是float类型的数据对象,而是float [5]类型的一维数组数据对象。
  在这个例子中,float类型的数据对象是构成一维数组的对象,指向float类型的指针最多只能+5。否则就是一种UB,这种行为是一种严重的软件安全隐患。
【原代码】

21.for(i=0;i<4;i++)
22.   {scanf("%d",pnum+i);
23.    for(j=0;j<5;j++)
24.      scanf("%f",pscore+5*i+j);
25.   }

【评析】
  应该写为

for( i = 0 ; i < 4 ; i++ )
{
   scanf("%d" , num + i );
   for( j = 0 ; j < 5 ; j++ )
      scanf("%f" , score[i] + j );
}

   pnum和pscore都是完全用不着的。当然,用函数完成这个功能更好。
【原代码】

26.paver=&aver[0];
27.printf("\n\n");
28.avsco(pscore,paver);

【评析】
  26.行完全是多余的。
  27.行很丑陋,应该写在相应的函数内。从效率上来说不如写puts("\n");
  28.行参数不完整,缺少两个数组第一维的维度;pscore,paver是两个完全不必要的变量,因为他们完全等价于*score,aver。
  下面再看avsco()函数的定义

【原代码】

37.void avsco(float *pscore,float *paver)
38.{int i,j;
39.float sum,average;
40.for(i=0;i<4;i++)
41.  {sum=0.0;
42.   for(j=0;j<5;j++)
43.    sum=sum+(*(pscore+5*i+j));
44.   average=sum/5;
45.   *(paver+i)=average;  
46.  }
47.}

【评析】
  首先函数的参数不完整,导致函数内有两个Magic Number :4,5。
  43.行的“*(pscore+5*i+j)”这种写法是错误的。前面提到过,这是一种UB。
  假使不考虑这个错误43.行也应该写为  

sum += *(pscore+5*i+j) ;

  这样显然更简洁。
  44.行混合运算,前面提过,这里不再赘述。
  39.行的average是一个完全多余的变量,因为44.行和45.行可以简单地写为:  

*(paver+i) = sum/5.0F;

   下面再回到main()。
【原代码】

29.avscour1(pcourse,pscore); 

【评析】
  这句很垃圾。
  首先,pcourse、pscore这两个变量是多余的,因为这两个实参无非是course和*score而已。
  其次,这个函数的功能并不满足“计算第一门课程的平均分”而是输出第一门课程的平均分。输出是main()的事情,而不是“计算”平均分的功能。
  第三,“计算第一门课程的平均分”这个功能要求提得很愚蠢。难道计算二门课程的平均分还要另外写一个函数不成?如果可以写一个计算第n门课程的平均分,没有人会愚蠢地提出“计算第一门课程的平均分”这样的函数功能要求。
  第四,作为“计算第一门课程的平均分”的函数,pcourse这个参数是多余的,因为这个参数对于计算是没有用处的。另一方面这个函数又缺少数组长度的参数。
  再来看一下avscour1()的函数定义:
【原代码】

49.void avscour1(char (*pcourse)[10],float *pscore)
50.{int i;
51.float sum,average1;
52.sum=0.0;
53.for(i=0;i<4;i++)
54.   sum=sum+(*(pscore+5*i));
55.average1=sum/4;
56.printf("course 1:%s average score:%7.2f\n",*pcourse,average1);
57.}

【评析】
  52.行,多余,应在定义sum时顺便初始化。
  53.行,这里有一个Magic Number:4,这是因为函数参数不完整造成的。
  54.行,这里同样存在数组越界的错误:“*(pscore+5*i)”
  54.行,不考虑越界错误,这句话应该写成

    sum +=*(pscore+5*i) ;

   这样代码更简洁。
  55.行,混合运算。
  56.行,从这里不难看出 average1这个变量是多余的。因为这句话可以写为

    printf("course 1:%s average score:%7.2f\n",*pcourse,sum/4.0F );

   29.行及49.行~57.行应写为:

int main( void )
{
   /*   */
   printf("course %s average score:%7.2f\n",course[1-1]
              ,average(score,5,1-1));//average(score , sizeof score / sizeof *score,1-1)
  /*  */
}

float  average( float p_score[][5],int  num_stu , int n)
{
   float  sum = 0.0 ;
   int  num_stu_ = num_stu ;
   while( num_stu_ -- > 0 )
   {
      sum += *(*p_score++ + n ) ;
   }   
   return sum / (float) num_stu ;
}

【原代码】

31.fali2(pcourse,pnum,pscore,paver);  

【评析】
  这个函数的4个参数同样很垃圾。很多初学者都容易犯这种幼稚病,他们偏执地只使用变量做实参,却不懂得实参是一个表达式。事实上那些变量是多余的。
  与此函数调用相应的函数类型声明:
【原代码】

05.void fali2(char course[5][10],int num[],float *pscore,float aver[4]);

【评析】
  同样丑陋不堪,其中的5和4是概念不清的表现,对应的函数定义也存在同样的毛病:
【原代码】

59.void fali2(char course[5][10],int num[],float *pscore,float aver[4])
60.{int i,j,k,label;
61.printf("  ==========Student who is fail in two courses=======\n");
62.printf("NO.");
63.for(i=0;i<5;i++)
64.   printf("%11s",course[i]);
65.printf("    average\n");  
66.for(i=0;i<4;i++)
67.{label=0;
68.  for(j=0;j<5;j++)
69.   if(*(pscore+5*i+j)<60.0)label++;
70.  if(label>=2)
71.   {printf("%d",num[i]);
72.    for(k=0;k<5;k++)
73.      printf("%11.2f",*(pscore+5*i+k));
74.    printf("%11.2f\n",aver[i]);  
75.   } 
76.}   
77.}

【评析】
  这个函数还有很多毛病:
  比如过于复杂;因为任务离数据过于遥远而导致参数过多。
  这是由于main()无所作为,没有对任务做适当的分解,只是把数据一股脑地推给了函数的缘故。
  同时也说明了问题要求把“找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩”写成一个函数十分荒谬愚蠢。
  61.~64.行和71.~74.行的代码完全应该写在这个函数的外部由单独的函数完成。
  67.行的label是莫名奇妙的名字。
  69.行的*(pscore+5*i+j)是错误的表达式,存在混合运算问题。
  70.行的代码效率低下,它应该与前面的if语句一起成为前面for循环的内部语句,即应写为:

for(j=0;j<5;j++)
{
       if(*(pscore+5*i+j)<60.0F)
           label++;
       if(label==2)
       {
         printf("%d",num[i]);
         for(k=0;k<5;k++)
            printf("%11.2f",*(pscore+5*i+k));
         printf("%11.2f\n",aver[i]);
         break ;  
       } 
}

   顺便说一句,这里的*(pscore+5*i+k)也是错误的表达式。
  此外,这个函数的函数名fali2也非常怪异,不清楚是哪国英语这样表述不及格。
【原代码】

33.good(pcourse,pnum,pscore,paver);

【评析】
  和前面fali2()函数调用存在同样的毛病,不再赘述。
【原代码】

79.void good(char course[5][10],int num[4],float *pscore,float aver[4])
80.   {int i,j,k,n;
81.  printf("  ==========Students whose score is good=======\n");
82.  printf("NO.");
83.  for(i=0;i<5;i++)
84.    printf("%11s",course[i]);
85.  printf("    average\n");
86.  for(i=0;i<4;i++)
87.   {n=0;
88.    for(j=0;j<5;j++)
89.      if(*(pscore+5*i+j)>85.0)n++;
90.    if(n==5||(aver[i]>=90))
91.    {printf("%d",num[i]);
92.     for(k=0;k<5;k++)
93.       printf("%11.2f",*(pscore+5*i+k));
94.     printf("%11.2f\n",aver[i]);  
95.    } 
96.}   
97.}

【评析】
  这个函数的毛病和fali2()的函数定义一样
  值得一提的是这段代码中的81.行~85.行居然和61.行~65.行完全一致,91.行~55.行居然和71.行~75.行完全一致。对任何程序员来说,这种代码都是一种耻辱。
  下面是对该问题及代码的修正。
——————————————————————————————
【修正】
  对题目的修正:
  有一个班4个学生,5门课程:
  ①要求计算第一门课程的平均分;
  ②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
  ③找出平均成绩在90分以上或全部成绩在85分以上的学生。
  注:原来要求“编写3个函数实现以上3个要求”实在太愚蠢。
【重构】

#include <stdio.h>             

#define NUM_STUDENT 4
#define NUM_COURSE 5
                  
int main( void )
{
  int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
  char  * course[NUM_COURSE] = {"English","Computer","Math","Physics","Chemistry"} ;
  double  score[NUM_STUDENT][NUM_COURSE] 
        = {
            { 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
            { 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
            { 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
            { 78.0 , 89.0 , 99.0 , 56.0 , 77.0 } 
          };


  return 0;
}

 注:因为题目并没有要求输入学号、课程名称、成绩,所以这些数据应该视同已知。此外如果使用结构体类型,数据结构将更为合理。
【分析】
  首先解决“①要求计算第一门课程的平均分;”
  这个问题的一般提法是,求4个学生的5门课程中第1门课程的平均分。
  程序员应该学会根据特殊问题提出具有一般性的问题并加以解决,而不能就事论事地盯着单独的具体问题。这样写出的代码才具有较好的通用性性、可复用性和可维护性。
  因此这个函数的函数原型应该是

double get_average( double score[][5] , int num_student , int order_course );

   其中,score:存储成绩的二维数组;num_student:学生人数;order_course :课程的序号。
  这样的函数,显然不但可以解决计算第一门课程的平均分这样的问题,也可以计算第 n (1<=n<=5)门课程的平均分这样的问题;不但可以解决4个学生情况下的这类问题,也可以解决k(k>0)个学生情况下的这类问题。
【重构】

#include <stdio.h>      

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int  , int  );                         
int main( void )
{
  int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
  char  * course[NUM_COURSE] = {"English","Computer","Math","Physics","Chemistry"} ;
  double  score[NUM_STUDENT][NUM_COURSE] 
        = {
            { 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
            { 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
            { 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
            { 78.0 , 89.0 , 99.0 , 56.0 , 77.0 } 
          };

   //①计算第一门课程的平均分;
  printf("第%d门课程%s的平均分为%7.2f\n" ,
           1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );

  return 0;
}
double get_average( double score[][NUM_COURSE] , int n , int k )
{
   double sum = .0 ;
   int i ;

   for( i = 0 ; i < n ; i ++ )
      sum += score[i][k];

   return sum/(double)n;
}

【分析】
  再看第二个要求:②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
  “找出”和“输出”是两件事情,不宜作为一个函数完成。此外“两门以上课程不及格的学生”是一个集合,除非构造出“集合”这种数据类型,否则单纯用函数无法完成,因为函数只能返回一个值。所以在“找出”这件事上main()不应该无所作为。
【重构】

#include <stdio.h>      

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int  , int  );  
void print_head(char  *([]), int );               
int less_than(double [], int  , double , int);    
void output(double [], int);     
int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char  * course[NUM_COURSE] = {"English","Computer","Math","Physics","Chemistry"} ;
double  score[NUM_STUDENT][NUM_COURSE] 
       = {
           { 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
           { 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
           { 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
           { 78.0 , 89.0 , 99.0 , 56.0 , 77.0 } 
         };

//①计算第一门课程的平均分;
printf("第%d门课程%s的平均分为%7.2f\n" ,
           1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );
           
//②找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
{
      int i;
      puts("\n两门课程以上课程不及格的学生");
      print_head( course , NUM_COURSE );     //输出表头 
      for( i = 0 ; i < NUM_STUDENT ; i ++ )
         if( less_than(score[i],NUM_COURSE,60.,2) ) //有2科成绩低于60.0 
         {
            printf("%d  ",num[i]);       //学号 
            output(score[i],NUM_COURSE); //输出各科成绩及平均分
         }
}    
       
return 0;
}

//输出一名学生各科成绩及平均分 
void output(double score[], int n)
{
   double sum = 0.0 ;
   int i ;
   for( i = 0 ; i < n ; i ++ )
   {
      printf("%11.2f",score[i]);
      sum += score[i] ;
   }
   printf( "%11.2f\n" , sum / (double) n );
}

//score中有amount科成绩低于fail返回1,否则返回0 
int less_than(double score[], int n , double fail , int amount )
{
   int i , num = 0 ;
   for( i = 0 ; i < n ; i ++ )
   {
      if( score[i] < fail )
         num ++ ;

      if( num == amount )
         return 1 ;
   }
   return 0;
}

//输出表头 
void print_head(char  *(course[]), int n )
{
   int i;
   printf("学号  ");
   for( i = 0 ; i < n ; i ++ )
   {
      printf("%11s",course[i]);
   }
   printf("   平均分\n");
   
}

// 返回score[n][5]中第k科平均分 
double get_average( double score[][NUM_COURSE] , int n , int k )
{
   double sum = .0 ;
   int i ;
   
   for( i = 0 ; i < n ; i ++ )
      sum += score[i][k];

   return sum/(double)n;
}

【分析】
  继续解决第三个问题:③找出平均成绩在90分以上或全部成绩在85分以上的学生。
  由于“平均成绩在90分以上或全部成绩在85分以上的学生”是一个集合,所以解决这个问题同样不适合用一个函数完成,而应该由main()和其他函数配合完成。      但是这里判断“平均成绩在90分以上”及“全部成绩在85分以上”应该分别由两个函数实现。
  而“全部成绩在85分以上”显然可以由前面写过的函数less_than()来表达:! less_than( score[ i ],NUM_COURSE,85.0 , 1 )。
  此外增加了一个求一名学生平均成绩的函数,并对前面的output()函数做了适当修改。
  最后的代码为:
【重构】

#include <stdio.h>      

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int  , int  );  
void print_head(char  *([]), int );               
int less_than(double [], int  , double , int);    
void output(double [], int);  
double get_average_student(double [], int);   
int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char  * course[NUM_COURSE] = {"English","Computer","Math","Physics","Chemistry"} ;
double  score[NUM_STUDENT][NUM_COURSE] 
       = {
           { 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
           { 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
           { 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
           { 78.0 , 89.0 , 99.0 , 56.0 , 77.0 } 
         };

//①计算第一门课程的平均分;
printf("第%d门课程%s的平均分为%7.2f\n" ,
           1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );

{//②找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
      int i ;
      puts("\n两门课程以上课程不及格的学生");
      print_head( course , NUM_COURSE );     //输出表头 
      for( i = 0 ; i < NUM_STUDENT ; i ++ )
         if( less_than(score[i],NUM_COURSE,60.,2) ) //有2科成绩低于60.0 
         {
            printf("%d  ",num[i]);       //学号 
            output(score[i],NUM_COURSE); //输出各科成绩及平均分
         }
}    

{//③找出平均成绩在90分以上或全部成绩在85分以上的学生。
      int i ;
      puts("\n平均成绩在90分以上或全部成绩在85分以上的学生有:");
      for( i = 0 ; i < NUM_STUDENT ; i ++ )
         if(  get_average_student(score[i],NUM_COURSE) > 90.0//平均成绩在90分以上
            ||!less_than( score[i],NUM_COURSE , 85.0 , 1 )   //全部成绩在85分以上
           )  
            printf("第%d号\n",num[i]);       //学号 
}      

return 0;
}

//求一名学生各科成绩平均分 
double get_average_student(double score[], int n)
{
   double sum = 0.0 ;
   int i ;
   for( i = 0 ; i < n ; i ++ )
      sum += score[i] ;

   return  sum / (double) n ;   
}

//输出一名学生各科成绩及平均分 
void output(double score[], int n)
{
   int i ;
   for( i = 0 ; i < n ; i ++ )
      printf("%11.2f",score[i]);
      
   printf( "%11.2f\n" , get_average_student( score , n) );
}

//score中有amount科成绩低于fail返回1,否则返回0 
int less_than(double score[], int n , double fail , int amount )
{
   int i , num = 0 ;
   for( i = 0 ; i < n ; i ++ )
   {
      if( score[i] < fail )
         num ++ ;

      if( num == amount )
         return 1 ;
   }
   return 0;
}

//输出表头 
void print_head(char  *(course[]), int n )
{
   int i;
   printf("学号  ");
   for( i = 0 ; i < n ; i ++ )
      printf("%11s",course[i]);

   printf("   平均分\n");
   
}

// 返回score[n][5]中第k科平均分 
double get_average( double score[][NUM_COURSE] , int n , int k )
{
   double sum = .0 ;
   int i ;
   
   for( i = 0 ; i < n ; i ++ )
      sum += score[i][k];

   return sum/(double)n;
}

 

posted @ 2012-05-18 16:35  garbageMan  阅读(3702)  评论(13编辑  收藏  举报