问题:


另一种阶乘

大家都知道阶乘这个概念,举个简单的例子: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[];
  7. int factorial(int x);
  8. printf("你想输入几组数据?\n");
  9. scanf("%d",&n);
  10. printf("请输入具体数值(1~20):\n");
  11.  
  12. for(i=;i<n;i++) //输入数值
  13. {
  14. scanf("%d",&a[i]);
  15.  
  16. while() //检验是否超出数值范围
  17. {
  18. if(a[i]> && a[i]<=)
  19. break;
  20. else
  21. {
  22. printf("超出范围,请重新输入\n");
  23. scanf("%d",&a[i]);
  24. }
  25. }
  26. }
  27.  
  28. for(i=;i<n;i++) //求阶乘之和
  29. {
  30. sum = ;
  31. for(j=;j<=a[i];j++)
  32. {
  33. temp=factorial(j);
  34. sum=sum+temp;
  35. }
  36. printf("%d\n",sum);
  37. }
  38.  
  39. return ;
  40. }
  41.  
  42. int factorial(int x) //求某一个整数X的阶乘
  43. {
  44. int i,temp;
  45. for(i=,temp=;i<=x;i+=)
  46. {
  47. temp=temp*i;
  48. }
  49.  
  50. return (temp);
  51. }

评析:


  这段代码完成功能应该没什么问题,但存在很多初学者容易犯的幼稚病。

  1. int n,i,j,temp,sum;
  2. int a[];

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

  变量定义多了,代码就变得混乱而不清晰。虽然有些变量后面会用到,但都是在局部使用。在局部使用的变量应该在局部定义。

  就这个问题而言,只有n是必须的。因为要记录输入。i是可由可无的。如果确定在main()中循环,那么需要这个i,否则连这个i都是多余的。

  权且假设在main()中需要写for循环语句,那么只要

  1. int n,i;

就可以了。这样看起来很清爽。

  1. int factorial(int x);

  把这个写在函数之内,非常莫名其妙。因为这使得这个声明的作用域局部化了。如果存在其他函数也需要调用factorial,那么就需要再写一回。这很不科学。

  虽然K&R第二版也这么写过,但我猜K&R并不是从实际应用的角度才那样写的。因为K&R第二版出版时,C标准并没有正式发表。新标准与K&R的C最显著的差别就是函数声明与定义的方式。K&R是从标准委员会内部人士那里知道标准修订情况的,并从委员会弄了个编译器测试。K&R这样写的目的应该只是强调函数声明新写法的格式及作用。事实上,后来没有人(尤其是在工程中)效法这种把函数类型声明写在局部的写法。

  1. for(i=;i<n;i++) //输入数值
  2. {
  3. scanf("%d",&a[i]);
  4.  
  5. while() //检验是否超出数值范围
  6. {
  7. if(a[i]> && a[i]<=)
  8. break;
  9. else
  10. {
  11. printf("超出范围,请重新输入\n");
  12. scanf("%d",&a[i]);
  13. }
  14. }
  15. }

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

  1. scanf("%d",&a[i]);
  2.  
  3. while() //检验是否超出数值范围
  4. {
  5. if(a[i]> && a[i]<=)
  6. break;
  7. else
  8. {
  9. printf("超出范围,请重新输入\n");
  10. scanf("%d",&a[i]);
  11. }
  12. }

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

  1. while( scanf("%d",&a[i]) , !(a[i]> && a[i]<=) )
  2. {
  3. printf("超出范围,请重新输入\n");
  4. }

  整个for语句应该这样

  1. for(i=;i<n;i++) //输入数值
  2. {
  3. int x ;//不用数组
  4. while( scanf("%d",&x ) , !( x > && x <= ) )
  5. {
  6. printf("超出范围,请重新输入\n");
  7. }
  8. //计算x!!和部分
  9. }

  这部分内容本身也可以抽象为一个函数,这时就连i这个变量也不需要在main()中定义。

  1. for(i=;i<n;i++) //求阶乘之和
  2. {
  3. sum = ;
  4. for(j=;j<=a[i];j++)
  5. {
  6. temp=factorial(j);
  7. sum=sum+temp;
  8. }
  9. printf("%d\n",sum);
  10. }

  这绝对是个败笔。因为前一条语句是同样循环变量且是同样次数的循环。形如

  1. for ( i = ; i < n ; i ++ )
  2. do_1st
  3.  
  4. for ( i = ; i < n ; i ++ )
  5. do_2nd

  这样的语句,通常都可以优化为

  1. for ( i = ; i < n ; i ++ )
  2. {
  3. do_1st
  4. do_2nd
  5. }

  现在就不难看出原代码中的数组a是不必要的了。作者原来使用数组是因为要穿越for语句,一旦不存在这样穿越的要求,就没必要用数组了,只用一个变量就可以了。
  所以,原来的两条for语句可以优化为

  1. for (i = ;i < n ; i++ ) //
  2. {
  3. int x ;
  4. int sum = ;
  5.  
  6. while (scanf("%d",&x) , !(x> && x<=) ) //输入数值,检验是否超出数值范围
  7. printf("超出范围,请重新输入\n");
  8.  
  9. while ( x > ) //求阶乘之和
  10. sum += factorial(x--);
  11.  
  12. printf("%d\n",sum);
  13. }

  最后再说说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) ) ) )

  原理不难理解,但用简洁的代码表达出来却不那么容易。我猜这是这个题目的本意。这是这个题目值得一做的地方。

  这个循环费我了大约十分钟,才勉强写成下面的样子。一个循环语句写这么长时间,对于我来说是比较罕见的事情。

  1. int add_factorial( int x ) //求"阶乘"和
  2. {
  3. int temp = x % ? - ( + ) : ( x-- , ) ;
  4.  
  5. do
  6. {
  7. temp += ( + ) ;
  8. temp *= x ;
  9. }
  10. while ( (x -= ) > );
  11.  
  12. return temp;
  13. }

  最后是重构的代码:

重构:


  1. /*
  2. 另一种阶乘
  3. 大家都知道阶乘这个概念,举个简单的例子:5!=1*2*3*4*5.
  4. 现在我们引入一种新的阶乘概念,将原来的每个数相乘变为i不大于n的所有奇数相乘
  5. 例如:5!!=1*3*5.现在明白现在这种阶乘的意思了吧!
  6. */
  7.  
  8. #include <stdio.h>
  9.  
  10. void solve( int );
  11. void input( int * );
  12.  
  13. int add_factorial(int x);
  14.  
  15. int main( void )
  16. {
  17. int n ;
  18.  
  19. printf("你想输入几组数据?\n");
  20. scanf("%d",&n);
  21.  
  22. solve( n );
  23.  
  24. return ;
  25. }
  26.  
  27. void input( int * p )
  28. {
  29. printf("请输入具体数值(1~20):\n");
  30. while (scanf( "%d", p ) , !( * p > && * p <= ) ) //输入数值,检验是否超出数值范围
  31. printf("超出范围,请重新输入\n");
  32. }
  33.  
  34. void solve( int n )
  35. {
  36.  
  37. while (n-- > )
  38. {
  39. int x ;
  40.  
  41. input( &x ) ;
  42. printf("%d\n" , add_factorial( x ) );
  43. }
  44.  
  45. }
  46.  
  47. int add_factorial( int x ) //求"阶乘"和
  48. {
  49. int temp = x % ? - ( + ) : ( x-- , ) ;
  50.  
  51. do
  52. {
  53. temp += ( + ) ;
  54. temp *= x ;
  55. }
  56. while ( (x -= ) > );
  57.  
  58. return temp;
  59. }

C语言初学者代码中的常见错误与瑕疵(2)的更多相关文章

  1. C语言初学者代码中的常见错误与瑕疵(23)

    见:C语言初学者代码中的常见错误与瑕疵(23)

  2. 一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)

    问题: 问题出处见 C语言初学者代码中的常见错误与瑕疵(5) . 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己 ...

  3. C语言初学者代码中的常见错误与瑕疵(5)

    问题: 素数 在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛. 当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将 ...

  4. C语言初学者代码中的常见错误与瑕疵(19)

    见:C语言初学者代码中的常见错误与瑕疵(19)

  5. C语言初学者代码中的常见错误与瑕疵(14)

    见:C语言初学者代码中的常见错误与瑕疵(14) 相关链接:http://www.anycodex.com/blog/?p=87

  6. 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)

    前文链接:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 重构 题目的修正 我抛弃了原题中“其中a, b, c, d是一个0-9的整数”这样的前提条件,因为这种限制毫无必要.只假设a, b ...

  7. C语言初学者代码中的常见错误与瑕疵(9)

    题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...

  8. 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)

    在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...

  9. C语言初学者代码中的常见错误与瑕疵(7)

    问题: 矩形的个数 在一个3*2的矩形中,可以找到6个1*1的矩形,4个2*1的矩形3个1*2的矩形,2个2*2的矩形,2个3*1的矩形和1个3*2的矩形,总共18个矩形.给出A,B,计算可以从中找到 ...

  10. C语言初学者代码中的常见错误与瑕疵(1)

    曾在豆瓣上看到过一个小朋友贴出他自己的代码(http://www.douban.com/group/topic/40293109/),当时随口指点了几句.难得这位小朋友虚心修正.从善如流,不断地改,又 ...

随机推荐

  1. [LeetCode]题解(python):054-Spiral Matrix

    题目来源 https://leetcode.com/problems/spiral-matrix/ Given a matrix of m x n elements (m rows, n column ...

  2. .net加载到vb 进程

    .net加载到vb 进程时,总是不能加载进去,什么原因呢? 要尝试三个步骤, 首先调试vb ,没有问题,代码能够调试 然后注册.net的dll,生成tlb文件,生成解决方案,调整附加到进程时的选项. ...

  3. 包含Blob字段的表无法Export/Import

    最近一直用MySQL-Front的导出导出工具完成数据库的备份,确实比较方便快捷. 后来增加了一张表,其中有blob字段,上传几个文件后,发现导出不好用了,进度条长期处于停滞状态. 想想也是,要把bl ...

  4. LeetCode Binary Search Tree Iterator

    原题链接在这里:https://leetcode.com/problems/binary-search-tree-iterator/ Implement an iterator over a bina ...

  5. JavaScript:表单验证模型

    之前做的验证提示以弹框的形式出现太丑陋了,不符合标准的验证提示.如果要想进行更好的数据验证操作,那么必须进行一些模块化设计,通过表单样式的改变来提示.其实,一般的数据验证无非就是那么几种,例如: 大多 ...

  6. MySQL学习笔记——存储引擎的索引特性

  7. Linux 中的零拷贝技术,第 2 部分

    技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...

  8. Power-BI 主要城市商品房销售分析

    经常在网上看到有关房价的讨论,房价可能真的悬了,高房价撑不了多久,一线城市房价远高于国际,暴涨游戏该结束了,等等.那么近年来房价整体上究竟是一个什么样的状态?今天我们撇开宏观经济要素,来看看近年来主要 ...

  9. Java框架基础——反射(reflect)

    一.Class类的使用 1)在面向对象(oop)的世界里,万事万物皆对象. 在Java中,包括基本的数据类型,都是对象. Class c = int.class;//int 的类类型 那就是说: 类是 ...

  10. BCP及自增标识列

    10:58 2012-12-20 通过BCP命令导入导出数据 bcp "test.dbo.lxy133" out d:\lxy133.txt -SMSSQL$SQL08R2 -Us ...