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_1stfor ( i = 0 ; i < n ; i ++ )do_2nd
这样的语句,通常都可以优化为
for ( i = 0 ; i < n ; i ++ ) {do_1stdo_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; }
C语言初学者代码中的常见错误与瑕疵(2)相关推荐
- C语言初学者代码中的常见错误与瑕疵(9)
题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...
- c语言一个数中是否含有8,要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)...
在 飞鸟_Asuka网友指出"是不是时间复杂度比较大",并说他"第一眼看到我就想把它当成一个数学问题来做"之后,我又重新对问题进行了数学式的思考后发现的. 这个 ...
- 为什么c语言加法错误,分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)...
重构 题目的修正 我抛弃了原题中"其中a, b, c, d是一个0-9的整数"这样的前提条件,因为这种限制毫无必要.只假设a, b, c, d是十进制整数形式的字符序列. 我也不清 ...
- c语言间接级别不同_一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)...
问题: 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己代码的改进和优化.标题只是为了保持系列的连续性. 改进 程 ...
- c语言编程过程中的常见错误,C语言编程常见错误与解决办法
warning: excess elements in array initializer 警告:数组初始值设定项中有多余元素 (定义的数组长度比赋值的个数小) 数组定义出错 "confli ...
- WCF项目中出现常见错误的解决方法:基础连接已经关闭: 连接被意外关闭
原文:WCF项目中出现常见错误的解决方法:基础连接已经关闭: 连接被意外关闭 在我们开发WCF项目的时候,常常会碰到一些莫名其妙的错误,有时候如果根据它的错误提示信息,一般很难定位到具体的问题所在,而 ...
- Android稳定性系列-01-使用 Address Sanitizer检测原生代码中的内存错误
前言 想必大家曾经被各种Native Crash折磨过,本地测试没啥问题,一到线上或者自动化测试就出现各种SIGSEGV.SIGABRT.SIGILL.SIGBUS.SIGFPE异常,而且堆栈还是崩溃 ...
- 中虚数怎么表示_英文论文写作中的常见错误
之前写过一篇如何写中文论文,这次就写个英文论文写作中的常见错误吧.都是平时自己整理总结的,也是一路摸爬滚打的见证吧.如有错误,欢迎批评指正.未完待续...... 1.逗号粘连: 两个独立的句子间要用句 ...
- SQL Server连接中的常见错误
SQL Server连接中的常见错误: 一."SQL Server 不存在或访问被拒绝" 这个是最复杂的,错误发生的原因比较多,需要检查的方面也比较多. 一般说来,有以下几种可能性 ...
最新文章
- redis系列:主从复制
- 模板格式丢失_公司法人私章证明丢失应该怎么办,需要补办吗?
- MySQL外键的设置及作用
- SAP Fiori Elements List Report 列表宽度决定逻辑的单步调试
- python自己的模块_Python--构建发布自己的模块
- python第三天习题
- 移动端与PC端页面布局区别
- 牛客网-华为机试题(python)
- linux 检查openssl,linux – 如何检查OpenSSL中的FIPS 140-2支持?
- 记一次feign调用报错:feign.codec.DecodeException: Error while extracting response for type [java...
- urllib2 爬虫 打印页面内容,部分无法显示
- 在金融科技的诸多技术领域,目前最引人瞩目的当属区块链
- 【案例分享】让新时代教育发展与“数”俱进
- 上传并下载excel表格
- Springboot列车调度信息系统的设计与实现4guf9计算机毕业设计-课程设计-期末作业-毕设程序代做
- 第6章 PPT页面排版与高级设计技术
- jquery获取当前时间戳的正确时间
- 经纬度和坐标之间怎么相互转换
- 微软表示今年员工不要参与愚人节活动!怕玩笑过大得不偿失
- python中的继承的初始化_python中子类继承父类的__init__方法实例