劣質代碼評析——兼談指針越界問題


【題目】
  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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM