上机题目:用各种方法求f=1-1/2!+1/3!-...+1/9!,并且要写在一个程序中。
目的在于锻炼各种循环。
提倡大家优先用for循环。计数型循环很方便,要习惯。
看题目,有点难度。题目显然将大家难住了。老师有责任,给大家跨度大了。大家要快进步,中间的招数你都领教过了,只不过现在要综合起来。
我带着大家把这个问题分解一下,由易到难解出来。
*********************问题的解决************************
(一)先做一个简单程序:f=1+2+...+9
太简单了!
int main() { const int n=9; //把9定义成常变量,这是个好办法,增强可读性、可维护性,见P30,讲过 int i,f; //本题求值后,f为整数,所以用int型。要根据情况选数据类型。i将用于控制循环,用int f=0; //赋初值很重要,也可以定义的同时初始化。有同学算出了莫名其妙的和,因为没有赋初值 for(i=1;i<=n;i++) f=f+i; //请深刻理解for有运行机制,要习惯于这种思维 cout<<f<<endl; }
在这段程序中,for可以有各种其他写法,但惟有此为最佳:i控制了循环的次数(理解为何叫计数型循环)。我们将i称作为循环变量。在for(;;)中两个分号隔开的三部分中,分别只给循环变量赋初值、判断是否结束循环、循环变量调整,其余的任何操作统统放到别的地方去做。例如此处,为f赋初值放在循环前、累加的工作作为循环体。除些之外,别的写法,如
for(f=0,i=1;i<=n;i++) f=f+i;
或
f=0,i=1; for(;i<=n;i++) f=f+i;
或
for(f=0,i=1;i<=n;f=f+i,i++);
或
f=0,i=1; for(;i<=n;) { f=f+i; i++; }
之流的,还可以写出无数,都是不利于理解的写法。
再欣赏一下正宗的for循环,想想前面的形式,那是什么东西!?
f=0; for(i=1;i<=n;i++) f=f+i;
(二)题目再变复杂些:f=1-2+3-4+...+9
int main() { const int n=9; int i,f,sign=1; //引入sign用于待加式子的符号变换 f=0; //赋初值很重要 for(i=1;i<=n;i++) { //因为循环体中将出现多条语句,加{}使之成为一条复合语句 f=f+sign*i; sign=-sign; //这个技术很重要,相比求幂,效率提高很多 } cout<<f<<endl; }
从这个例子中,我想强调:设计程序时,每一个变量的含义必须明确,不要试图让一个变量承担过多的角色。有些同学的变量用得很乱,碰巧得到正确的结果,有时还沾沾自喜,孰料其中存在大大的隐患。
sign变量的名字也再次告诉我们,变量起名时,要尽量起有意义的名字,尤其其中几个变量间的关系微妙时。小程序中常用n、i、f之类的,其实并不是好习惯。想想dTax、fNetIncome,何等的清楚。
(三)还再变复杂些:f=1-1/2+1/3-1/4+...+1/9。
int main() { const int n=9; int i,sign=1; double f; //为什么换类型了?算下来f是小数,还用整型那不找错吗? f=0; for(i=1;i<=n;i++) { f=f+sign*double(1)/i; //这儿涉及类型转换,下面将深入讨论 sign=-sign; //sign只用作控制符号,不作他用,欣赏一下 } cout<<f<<endl; }
关于类型,因为i为int型,如果直接1/i结果为int型,因为i≥1,从第2次循环开始(i=2),f就一直累加0了,多么可怕的一个潜在Bug。也可以写作1/double(i),但不能是double(1/i),已经求得商为0了,再double有何用,无法起死回生了。从语句整体,写成f=f+sign/double(i);更好。
在同学学愿意将i用作double型或float型,这样f=f+sign*1/i;或f=f+sign/i;都行。但是i是用于计数的(顺便做了除数而已)这个含义将被削弱。我个人还是不赞同这样做,尽管需要进行强制类型转换。
(四)在(二)的基础上再做次复杂化:f=1!-2!+3!-4!+...+9!
增加一个变量a用于表示等待累加的阶乘值,显然,在累加3!时,上一循环计算得到的阶乘a为2!,a*3即是要加的3!。
int main() { const int n=9; int i,f,a,sign=1; //引入sign用于待加式子的符号变换 f=0; //累加和的初值取0 a=1; //累乘积的初值取1 for(i=1;i<=n;i++) { a=a*i; f=f+sign*a; //将阶乘加上去,不要试图将求幂也写到一个语句中,那样的程序没法读了 sign=-sign; } cout<<f<<endl; }
(五)到此应该知道:将(三)和(四)结合起来就是我们要的结果了!
计算:f=1-1/2!+1/3!-...+1/9!
#include <iostream> using namespace std; int main() { const int n=9; int i,a,sign=1; double f; f=0; a=1; //再说一点,很多同学将这两个语句写在一行,没错,但不好 for(i=1;i<=n;i++) { a=a*i; f=f+sign*double(1)/a; sign=-sign; } cout<<f<<endl; }
*********************用while循环实现************************
给出循环:
for(i=1;i<=n;i++) f=f+i;
对应的while循环是:
i=1; while(i<=n) { f=f+i; i++; }
仍然是一个漂亮的计数!只不过将要做的事情在合适的位置上安排好罢了。
用while实现计算f=1-1/2!+1/3!-...+1/9!的程序是
#include <iostream> using namespace std; int main() { const int n=9; int i,a,sign=1; double f; f=0; a=1; i=1; //对应原先for循环中的<表达式1>,—— for(i=1;i<=n;i++) while(i<=n) //循环条件是原for循环中的<表达式2>,—— for(i=1;i<=n;i++) { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; //不要漏下原for循环中的<表达式3>,—— for(i=1;i<=n;i++) } cout<<f<<endl; }
*********************用do-while循环实现************************
给出循环:
for(i=1;i<=n;i++) f=f+i;
如果能够保证循环体至少能执行1次,对应的do-while循环是:
i=1; do { f=f+i; i++; } while(i<=n);
换汤不换药,关键在于,对这种循环的运行过程要了然于胸。
for循环和while循环的循环体允许一次都不执行,如果循环条件一开始就不满足。而do-while循环的循环体则最少执行一行。为此,有些问题用do-while写时就有其特点了,此处不表。
用do-while实现计算f=1-1/2!+1/3!-...+1/9!的程序是:
#include <iostream> using namespace std; int main() { const int n=9; int i,a,sign=1; double f; f=0; a=1; i=1; //循环的初始条件 do { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; //循环变量的变化写在循环体内 }while(i<=n); //确定在何条件下循环可以继续 cout<<f<<endl; }
*********************将三个循环写到一个程序中完成************************
先写错误的做法。
错误做法1:将上述三个程序写在一个文件中
#include <iostream> using namespace std; int main() { for... } #include <iostream> using namespace std; int main() { while... } #include <iostream> using namespace std; int main() { do... }
出这个错误的同学,从第一章开始看起,理解:
(1)诸如下面的语句写在最前面,且不能多写
#include <iostream> using namespace std;
最为重要的是,main()函数是程序执行的入口,main()函数只能有一个。
错误做法2:简单拼接,影响最大。哪些保留,哪些得去掉?
#include <iostream> using namespace std; int main() { const int n=9; int i,a,sign=1; double f=0; //定义变量的同时初始化 a=1; i=1; while(i<=n) { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; //不要漏下原for循环中的<表达式3>,—— for(i=1;i<=n;i++) } cout<<"(1)用while循环完成: "<<f<<endl; int i,a,sign=1; //应该改成sign=1: i和a不能再定义;若全不要,退出for循环时sign值为-1,下面累加的第一项就是-1了。 double f; // 一定要去掉,变量的定义是分配空间,已经定义过了,且其使命已经完成,可以在下面直接挪作他用 //下面两个循环前的赋值必须有,这是出问题最多的地方! f=0; //如果缺这个,后面的赋值是在已经求得的结果基础上再累加的! a=1; //如果缺这个,for中累加的第1项是1/9! for(i=1;i<=n;i++) //有人将i=1也省掉了,结果是,这个循环根本不会执行,后果很严重... { a=a*i; f=f+sign*double(1)/a; sign=-sign; } cout<<"(2)用for循环完成: "<<f<<endl; //...如果i=1省掉,循环不执行,输出正确,但功劳属于while //下面哪些该留哪些不留,请思考...... int i,a,sign=1; double f; f=0; a=1; i=1; do { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; }while(i<=n); cout<<"(3)用do-while循环完成: "<<f<<endl; }
实际上,按“自顶向下,逐步求精”的思路,此任务分成三部分,做完一个再做一个,写在一个程序中,很自然的一个“顺序结构”。我现在才发现顺序结构貌似最容易,但水不浅。
于是,正确的程序,更重要的是漂亮的程序形如:
#include <iostream> using namespace std; int main() { const int n=9; int i,a,sign=1; double f=0; //定义变量的同时初始化 a=1; i=1; while(i<=n) { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; //不要漏下原for循环中的<表达式3>,—— for(i=1;i<=n;i++) } cout<<"(1)用while循环完成: "<<f<<endl; sign=1; f=0; a=1; for(i=1;i<=n;i++) { a=a*i; f=f+sign*double(1)/a; sign=-sign; } cout<<"(2)用for循环完成: "<<f<<endl; sign=1; f=0; a=1; i=1; do { a=a*i; f=f+sign*double(1)/a; sign=-sign; i++; }while(i<=n); cout<<"(3)用do-while循环完成: "<<f<<endl; }