C語言初學者代碼中的常見錯誤與瑕疵(2)


問題:


另一種階乘

大家都知道階乘這個概念,舉個簡單的例子:5!=1*2*3*4*5.

現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘

例如:5!!=1*3*5.現在明白現在這種階乘的意思了吧!

原代碼:


 1 #include <stdio.h>
 2 
 3 int main()  4 {  5    int n,i,j,temp,sum;  6    int a[20];  7    int factorial(int x);  8    printf("你想輸入幾組數據?\n");  9    scanf("%d",&n); 10    printf("請輸入具體數值(1~20):\n"); 11    
12    for(i=0;i<n;i++) //輸入數值 
13  { 14       scanf("%d",&a[i]); 15       
16       while(1) //檢驗是否超出數值范圍 
17  { 18          if(a[i]>0 && a[i]<=20) 19             break; 20          else 
21  { 22             printf("超出范圍,請重新輸入\n"); 23             scanf("%d",&a[i]); 24  } 25  } 26  } 27    
28    for(i=0;i<n;i++) //求階乘之和 
29  { 30       sum = 0; 31       for(j=1;j<=a[i];j++) 32  { 33          temp=factorial(j); 34          sum=sum+temp; 35  } 36       printf("%d\n",sum); 37  } 38    
39    return 0; 40 } 41 
42 int factorial(int x) //求某一個整數X的階乘 
43 { 44    int i,temp; 45    for(i=1,temp=1;i<=x;i+=2) 46  { 47       temp=temp*i; 48  } 49    
50    return (temp); 51 } 

評析:


  這段代碼完成功能應該沒什么問題,但存在很多初學者容易犯的幼稚病。

   int n,i,j,temp,sum; int a[20]; 

  首先,定義的變量太多。很多都是毫無必要的。比如j、temp、sum以及a[]。

  變量定義多了,代碼就變得混亂而不清晰。雖然有些變量后面會用到,但都是在局部使用。在局部使用的變量應該在局部定義。

  就這個問題而言,只有n是必須的。因為要記錄輸入。i是可由可無的。如果確定在main()中循環,那么需要這個i,否則連這個i都是多余的。

  權且假設在main()中需要寫for循環語句,那么只要

   int n,i;

就可以了。這樣看起來很清爽。

   int factorial(int x); 

  把這個寫在函數之內,非常莫名其妙。因為這使得這個聲明的作用域局部化了。如果存在其他函數也需要調用factorial,那么就需要再寫一回。這很不科學。

  雖然K&R第二版也這么寫過,但我猜K&R並不是從實際應用的角度才那樣寫的。因為K&R第二版出版時,C標准並沒有正式發表。新標准與K&R的C最顯著的差別就是函數聲明與定義的方式。K&R是從標准委員會內部人士那里知道標准修訂情況的,並從委員會弄了個編譯器測試。K&R這樣寫的目的應該只是強調函數聲明新寫法的格式及作用。事實上,后來沒有人(尤其是在工程中)效法這種把函數類型聲明寫在局部的寫法。

   for(i=0;i<n;i++) //輸入數值 
 { scanf("%d",&a[i]); while(1) //檢驗是否超出數值范圍 
 { if(a[i]>0 && a[i]<=20) break; else { printf("超出范圍,請重新輸入\n"); scanf("%d",&a[i]); } } } 

  這個有些喧賓奪主了,沒必要把輸入寫得這么復雜。另外應該把a[]定義這個循環體局部。進一步思考一下的話,不難發現,根本不需要用數組,一個簡單的int類型變量就可以了。

      scanf("%d",&a[i]); while(1) //檢驗是否超出數值范圍 
 { if(a[i]>0 && a[i]<=20) break; else { printf("超出范圍,請重新輸入\n"); scanf("%d",&a[i]); } } 

  風格很差,太不C了。應該

      while( scanf("%d",&a[i]) , !(a[i]>0 && a[i]<=20) ) { printf("超出范圍,請重新輸入\n"); } 

  整個for語句應該這樣

   for(i=0;i<n;i++) //輸入數值 
 { int x ;//不用數組
      while( scanf("%d",&x ) , !( x > 0 && x <= 20 ) ) { printf("超出范圍,請重新輸入\n"); } //計算x!!和部分
   } 

  這部分內容本身也可以抽象為一個函數,這時就連i這個變量也不需要在main()中定義。

   for(i=0;i<n;i++) //求階乘之和 
 { sum = 0; for(j=1;j<=a[i];j++) { temp=factorial(j); sum=sum+temp; } printf("%d\n",sum); } 

  這絕對是個敗筆。因為前一條語句是同樣循環變量且是同樣次數的循環。形如

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

  這樣的語句,通常都可以優化為

for ( i = 0 ; i < n ; i ++ ) { do_1st do_2nd }

  現在就不難看出原代碼中的數組a是不必要的了。作者原來使用數組是因為要穿越for語句,一旦不存在這樣穿越的要求,就沒必要用數組了,只用一個變量就可以了。
  所以,原來的兩條for語句可以優化為

   for (i = 0 ;i < n ; i++ ) // 
 { int x ; int sum = 0 ; while (scanf("%d",&x) , !(x>0 && x<=20) ) //輸入數值,檢驗是否超出數值范圍 
         printf("超出范圍,請重新輸入\n"); while ( x > 0 )            //求階乘之和
          sum += factorial(x--); printf("%d\n",sum); } 

  最后再說說factorial()函數。

  首先,作者用一個函數求這種另類階乘的值很好。但是由於問題是求這種另類階乘的和,所以這種寫法存在重復計算的問題。舉例來說,當x為3時

  1!! + 2!! + 3!!

  這種方法在計算2!!和3!!時會重復求1!!,在求3!!時會重復求2!!。因而效率較低。

  如果希望效率更高,應該考慮直接求這種另類階乘的和。

  具體算法原理如下:
    1!! + 2!! + 3!! + …… + x!!
  = 1!! + 1!! + 3!! + 3!! + …… + x!! (x為奇數時)

    1!! + 2!! + 3!! + …… + x!!
  = 1!! + 1!! + 3!! + 3!! + …… + (x-1)!! (x為偶數時)

  第一個式子

    1!! + 2!! + 3!! + …… + x!!
  = 1 * ( 1 + 1 + 3 * ( 1 + 1 +  5 * ( 1 + 1 + …… + x ( 1 + 0 ) ) ) ) 

  第二個式子

    1!! + 2!! + 3!! + …… + (x-3)!! + (x-3)!! + (x-1)!!
  = 1 * ( 1 + 1 + 3 * ( 1 + 1 +  5 * ( 1 + 1 + …… + (x - 1)( 1 + 1) ) ) ) 

  原理不難理解,但用簡潔的代碼表達出來卻不那么容易。我猜這是這個題目的本意。這是這個題目值得一做的地方。

  這個循環費我了大約十分鍾,才勉強寫成下面的樣子。一個循環語句寫這么長時間,對於我來說是比較罕見的事情。

int add_factorial( int x ) //求"階乘"和 
{ int temp = x % 2 ? 1 - ( 1 + 1 ) : ( x-- , 0 ) ; do { temp += ( 1 + 1 ) ; temp *= x ; } while ( (x -= 2 ) > 0 ); return temp; }

  最后是重構的代碼:

重構:


/* 另一種階乘 大家都知道階乘這個概念,舉個簡單的例子:5!=1*2*3*4*5. 現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘 例如:5!!=1*3*5.現在明白現在這種階乘的意思了吧! */ #include <stdio.h>

void solve( int ); void input( int * ); int add_factorial(int x); int main( void ) { int n ; printf("你想輸入幾組數據?\n"); scanf("%d",&n); solve( n ); return 0; } void input( int * p ) { printf("請輸入具體數值(1~20):\n"); while (scanf( "%d", p ) , !( * p > 0 && * p <= 20 ) ) //輸入數值,檢驗是否超出數值范圍 
      printf("超出范圍,請重新輸入\n"); } void solve( int n ) { while (n-- > 0) { int x ; input( &x ) ; printf("%d\n" , add_factorial( x ) ); } } int add_factorial( int x ) //求"階乘"和 
{ int temp = x % 2 ? 1 - ( 1 + 1 ) : ( x-- , 0 ) ; do { temp += ( 1 + 1 ) ; temp *= x ; } while ( (x -= 2 ) > 0 ); return temp; }

 

 


免責聲明!

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



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