C语言初学者代码中的常见错误与瑕疵(2)
问题:
另一种阶乘
大家都知道阶乘这个概念,举个简单的例子:5!=1*2*3*4*5.
现在我们引入一种新的阶乘概念,将原来的每个数相乘变为i不大于n的所有奇数相乘
例如:5!!=1*3*5.现在明白现在这种阶乘的意思了吧!
原代码:
#include <stdio.h> int main()
{
int n,i,j,temp,sum;
int a[];
int factorial(int x);
printf("你想输入几组数据?\n");
scanf("%d",&n);
printf("请输入具体数值(1~20):\n"); for(i=;i<n;i++) //输入数值
{
scanf("%d",&a[i]); while() //检验是否超出数值范围
{
if(a[i]> && a[i]<=)
break;
else
{
printf("超出范围,请重新输入\n");
scanf("%d",&a[i]);
}
}
} for(i=;i<n;i++) //求阶乘之和
{
sum = ;
for(j=;j<=a[i];j++)
{
temp=factorial(j);
sum=sum+temp;
}
printf("%d\n",sum);
} return ;
} int factorial(int x) //求某一个整数X的阶乘
{
int i,temp;
for(i=,temp=;i<=x;i+=)
{
temp=temp*i;
} return (temp);
}
评析:
这段代码完成功能应该没什么问题,但存在很多初学者容易犯的幼稚病。
int n,i,j,temp,sum;
int a[];
首先,定义的变量太多。很多都是毫无必要的。比如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=;i<n;i++) //输入数值
{
scanf("%d",&a[i]); while() //检验是否超出数值范围
{
if(a[i]> && a[i]<=)
break;
else
{
printf("超出范围,请重新输入\n");
scanf("%d",&a[i]);
}
}
}
这个有些喧宾夺主了,没必要把输入写得这么复杂。另外应该把a[]定义这个循环体局部。进一步思考一下的话,不难发现,根本不需要用数组,一个简单的int类型变量就可以了。
scanf("%d",&a[i]); while() //检验是否超出数值范围
{
if(a[i]> && a[i]<=)
break;
else
{
printf("超出范围,请重新输入\n");
scanf("%d",&a[i]);
}
}
风格很差,太不C了。应该
while( scanf("%d",&a[i]) , !(a[i]> && a[i]<=) )
{
printf("超出范围,请重新输入\n");
}
整个for语句应该这样
for(i=;i<n;i++) //输入数值
{
int x ;//不用数组
while( scanf("%d",&x ) , !( x > && x <= ) )
{
printf("超出范围,请重新输入\n");
}
//计算x!!和部分
}
这部分内容本身也可以抽象为一个函数,这时就连i这个变量也不需要在main()中定义。
for(i=;i<n;i++) //求阶乘之和
{
sum = ;
for(j=;j<=a[i];j++)
{
temp=factorial(j);
sum=sum+temp;
}
printf("%d\n",sum);
}
这绝对是个败笔。因为前一条语句是同样循环变量且是同样次数的循环。形如
for ( i = ; i < n ; i ++ )
do_1st for ( i = ; i < n ; i ++ )
do_2nd
这样的语句,通常都可以优化为
for ( i = ; i < n ; i ++ )
{
do_1st
do_2nd
}
现在就不难看出原代码中的数组a是不必要的了。作者原来使用数组是因为要穿越for语句,一旦不存在这样穿越的要求,就没必要用数组了,只用一个变量就可以了。
所以,原来的两条for语句可以优化为
for (i = ;i < n ; i++ ) //
{
int x ;
int sum = ; while (scanf("%d",&x) , !(x> && x<=) ) //输入数值,检验是否超出数值范围
printf("超出范围,请重新输入\n"); while ( x > ) //求阶乘之和
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 % ? - ( + ) : ( x-- , ) ; do
{
temp += ( + ) ;
temp *= x ;
}
while ( (x -= ) > ); 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 ;
} void input( int * p )
{
printf("请输入具体数值(1~20):\n");
while (scanf( "%d", p ) , !( * p > && * p <= ) ) //输入数值,检验是否超出数值范围
printf("超出范围,请重新输入\n");
} void solve( int n )
{ while (n-- > )
{
int x ; input( &x ) ;
printf("%d\n" , add_factorial( x ) );
} } int add_factorial( int x ) //求"阶乘"和
{
int temp = x % ? - ( + ) : ( x-- , ) ; do
{
temp += ( + ) ;
temp *= x ;
}
while ( (x -= ) > ); return temp;
}
C语言初学者代码中的常见错误与瑕疵(2)的更多相关文章
- C语言初学者代码中的常见错误与瑕疵(23)
见:C语言初学者代码中的常见错误与瑕疵(23)
- 一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)
问题: 问题出处见 C语言初学者代码中的常见错误与瑕疵(5) . 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己 ...
- C语言初学者代码中的常见错误与瑕疵(5)
问题: 素数 在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛. 当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将 ...
- C语言初学者代码中的常见错误与瑕疵(19)
见:C语言初学者代码中的常见错误与瑕疵(19)
- C语言初学者代码中的常见错误与瑕疵(14)
见:C语言初学者代码中的常见错误与瑕疵(14) 相关链接:http://www.anycodex.com/blog/?p=87
- 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)
前文链接:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 重构 题目的修正 我抛弃了原题中“其中a, b, c, d是一个0-9的整数”这样的前提条件,因为这种限制毫无必要.只假设a, b ...
- C语言初学者代码中的常见错误与瑕疵(9)
题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...
- 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)
在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...
- 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,计算可以从中找到 ...
- C语言初学者代码中的常见错误与瑕疵(1)
曾在豆瓣上看到过一个小朋友贴出他自己的代码(http://www.douban.com/group/topic/40293109/),当时随口指点了几句.难得这位小朋友虚心修正.从善如流,不断地改,又 ...
随机推荐
- oracle 把一个用户的表结构导入到另一个用户下
create table AM_CONTENTS as select * from bizdata008.AM_CONTENTS where 1=2
- 折腾Centos6.4记
背景: 闲置了一台Thinkpad,之前装的是Kali Linux,但无线网卡挂掉了,加之硬盘分区不当,平时几乎没怎么用,重新使用kali的livecd进行分区,然后安装,总是出错,尝试了七八次,仍然 ...
- Selenium2学习-009-WebUI自动化实战实例-007-Selenium 8种元素定位实战实例源代码(百度首页搜索录入框及登录链接)
此 文主要讲述用 Java 编写 Selenium 自动化测试脚本编写过程中,通过 ID.name.xpath.cssSelector.linkText.className.partialLinkTe ...
- JVM底层又是如何实现synchronized的
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...
- Condition
1.Condition是个接口,其实现类是同步器里面的一个内部静态类:ConditionObject. 2.Lock是个接口,该接口里面有个方法是:Condition newCondition(); ...
- @MyBatis中的if...else...
<select id="selectSelective" resultMap="xxx" parameterType="xxx"> ...
- li有无缩略图样式调取
<div class="part3list">[field:array runphp='yes']@me = (strpos(@me['litpic'],'defaul ...
- iOS:消除项目中警告
引言: 在iOS开发过程中, 我们可能会碰到一些系统方法弃用, weak.循环引用.不能执行之类的警告. 有代码洁癖的孩子们很想消除他们, 今天就让我们来一次Fuck 警告!! 首先学会基本的语句: ...
- lodash的运用
Object 对象 1._.merge(object, [sources]) 此方法类似于_.assign,除了它递归地将源对象的自有和继承的可枚举字符串键控属性合并到目标对象中. 如果存在目标值,将 ...
- 还原SQLServer2008数据库报用户无法登录 .
在一台新的服务器上还原mssql2008r2数据库后,原来数据库中的账户无法用来打开这台新还原的数据库,报错:登录失败 错误代码:4064. 分析原因:在备份数据库的时候,服务器引擎中的安全-> ...