• 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("你想输入几组数据?
    "); 
     9    scanf("%d",&n); 
    10    printf("请输入具体数值(1~20):
    "); 
    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("超出范围,请重新输入
    "); 
    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
    ",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("超出范围,请重新输入
    "); 
                scanf("%d",&a[i]); 
             } 
          } 
       } 

      这个有些喧宾夺主了,没必要把输入写得这么复杂。另外应该把a[]定义这个循环体局部。进一步思考一下的话,不难发现,根本不需要用数组,一个简单的int类型变量就可以了。

          scanf("%d",&a[i]); 
          
          while(1) //检验是否超出数值范围 
          { 
             if(a[i]>0 && a[i]<=20) 
                break; 
             else 
             { 
                printf("超出范围,请重新输入
    "); 
                scanf("%d",&a[i]); 
             } 
          } 

      风格很差,太不C了。应该

          while( scanf("%d",&a[i]) , !(a[i]>0 && a[i]<=20) )  
          { 
             printf("超出范围,请重新输入
    "); 
          } 

      整个for语句应该这样

       for(i=0;i<n;i++) //输入数值 
       { 
          int x ;//不用数组
          while( scanf("%d",&x ) , !( x > 0 && x <= 20 ) )  
          { 
             printf("超出范围,请重新输入
    "); 
          } 
          //计算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
    ",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("超出范围,请重新输入
    "); 
    
          while ( x > 0 )            //求阶乘之和
              sum += factorial(x--);
    
          printf("%d
    ",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("你想输入几组数据?
    "); 
       scanf("%d",&n); 
    
       solve( n ); 
       
       return 0;
    }
    
    void input( int * p )
    {
       printf("请输入具体数值(1~20):
    ");
       while (scanf( "%d", p ) , !( * p > 0 && * p <= 20 ) ) //输入数值,检验是否超出数值范围 
          printf("超出范围,请重新输入
    "); 
    }
    
    void solve( int n )
    {
       
       while (n-- > 0)
       {
          int x ;
          
          input( &x ) ;
          printf("%d
    " , 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; 
    }
  • 相关阅读:
    Yii2 国际化的问题 zh-CN
    Yii2归档安装法
    MySQL性能优化的最佳20+条经验
    Jquery 选择器汇总
    关于MooTools你应该熟知的6个基本知识
    Android开发效率—Eclipse快捷键
    Failed to fetch URL http://dl-ssl.google.com/android/repository/addons_list-2.xml, reason: Connectio (andriod sdk manager) http://dl-ssl.google.com/android上不去解决方案
    Windows下搭建objective C开发环境
    android studio创建项目
    android studio 安装与环境搭建
  • 原文地址:https://www.cnblogs.com/pmer/p/3422201.html
Copyright © 2020-2023  润新知