1、第 4 章 选择与循环结构程序设计选择结构与循环结构是程序设计中重要的两种基本结构,通过本章的学习熟练掌握分支结构语句的格式和功能,能正确选取分支语句来设计选择结构程序;熟练掌握循环结构语句的格式和功能,并能根据循环结构的要求正确选取循环语句来实现循环。4.1 选择结构(分支结构)程序在 C 语言中选择结构是用 if 语句实现的,if 语句典型的形式是:if(表达式) 语句 1 else 语句 2其中的表达式最常见的是关系表达式和逻辑表达式。因此本节在介绍关系表达式和逻辑表达式的基础上,阐述在 C 语言中选择结构的实现以及设计选择结构的程序。4.1.1 关系运算符与关系表达式关系运算是逻辑运算
2、中比较简单的一种。它实质上是两个值之间的“比较运算” ,其运算结果只有两种:“真” 、 “假” 。因此关系表达式的运算结果也只有两种:“真” 、 “假” 。在 C 语言里, “真”是任意非 0 的值, “假”就是 0。关系表达式的结果若为“真” ,则返回1;若为“假” ,则返回 0。C 语言提供 6 种关系运算符: (大于)、= (大于等于 )、,=, = 3 12*5= =3 08!=8 01!=34 14.1.2 逻辑运算符与逻辑表达式逻辑运算符可以将多个关系表达式或逻辑量连接起来,构成逻辑表达式。C 语言里提供了 3 种逻辑运算符: !(非) ,x=100;printf(“%d“,x10
3、);输出结果为 1。(2)C 语言在计算逻辑表达式时,如果在某一步已得到了整个表达式的结果,则后面的部分将不再计算。对于逻辑“与” (c=a|+b;这里因为 a 的值 1(真) ,故+ +b 没有操作,b 的值仍为 0。c=b这里因为 b 的值 0(假) ,故 a+ +没有操作,a 的值仍为 1。(3)注意与数学式子的区别。例如:int a=8,b=5,c=2; 数学式子 abc,在 C 语言中应这样写:abprintf(“input two numbers: “);scanf(“%d,%d“,max=a;if (maxb)printf(“max=%dn“,a);elseprintf(“max
4、=%dn“,b);此例用 if-else 语句判别 a,b 的大小,若 a 比 b 大,则输出 a,否则输出 b。(3) if-else if 形式前二种形式的 if 语句一般都用于两个分支的情况。 当有多个分支选择时(如图 4.3所示) ,可采用 if-else if 语句,其一般形式为:if(表达式 1) 语句 1else if(表达式 2) 语句 2else if(表达式 3) 语句 3else if(表达式 m) 语句 melse 语句 n其语义是:依次判断表达式的值,当出现某个值为真时, 则执行其对应的语句;如果所有的表达式均为假,则执行语句 n 。无论执行了哪条语句,执行后跳到整个
5、 if 语句之外继续图 4.1 基本 if 语句图 4.2 if-else 语句执行程序,而不再对后面的表达式进行判断。【例 4.3】 从键盘输入学生成绩,判断成绩属于“优” 、 “良” 、 “中” 、 “及格” 、 “不及格”中的等级并输出。main()int score;printf(“输入成绩:“);scanf(“%d“,if (score100) printf(“成绩输入不正确n“);else if (scoreb) a+;b+;else a=0;b=10;2. if 语句的嵌套当 if 语句中的执行语句又是 if 语句时,则构成了 if 语句嵌套的情形。其一般形式为:图 4.3 if
6、-else if 语句if(表达式)if 语句elseif 语句在嵌套内的 if 语句可能又是 if-else 型的,这时将会出现多个 if 和多个 else 重叠的情况,这时要特别注意 if 和 else 的配对问题。例如:if(表达式 1)if(表达式 2)语句 1;else语句 2;其中的 else 究竟是与哪一个 if 配对呢?应该理解为: 还是应理解为:if(表达式 1) if(表达式 1)if(表达式 2) if(表达式 2)语句 1; 语句 1;else else语句 2; 语句 2;为了避免这种二义性,语言规定,else 总是与它前面最近的未配对的 if 配对。因此对上例应按后
7、一种情况理解。【例 4.4】 比较两个数的大小关系。程序 1:main()int a,b;printf(“please input A,B: “);scanf(“%d,%d“,if(a!=b)if(ab) printf(“ABn“);else printf(“AB、Ab) printf(“ABn“);程序 2:main()int a,b;printf(“please input A,B: “);scanf(“%d,%d“,if(a=b) printf(“A=Bn“);else if(ab) printf(“ABn“);else printf(“Ay 则将 x 与 y 的值进行交换,这样 x 就
8、是前两个数中较小的数;然后再用 x 与 z 进行比较,如果 xz 则将 x 与 z 的值进行交换,这样x 就是三个数中最小的。最后再比较和排列 y 与 z。程序源代码如下:main()int x,y,z,t;printf(“n Input x,y,z: “);scanf(“%d,%d,%d“,if (xy)t=x;x=y;y=t; /*交换 x,y 的值*/if(xz)t=z;z=x;x=t; /*交换 x,z 的值*/if(yz)t=y;y=z;z=t; /*交换 z,y 的值*/printf(“Small to big: %d %d %dn“,x,y,z);【例 4.8】 写一个程序,求一
9、元二次方程的根。程序的算法分析见图 4.4。显然程序适合使用 if 语句的嵌套结构。#include#includemain()float a,b,c,delta,term1,term2;printf(“enter a,b,c:“);scanf(“%f,%f,%f“,if (a=0)if(b=0) printf(“no answer due to input error!n“);else printf(“the single root is %fn“,-c/b);elsedelta=b*b-c*a*c;term1=-b/(2*a);term2=sqrt(abs(delta)/ (2*a);if
10、 (delta#includemain()图 4.4 求一元二次方程的根long int i,bonus1,bonus2,bonus4,bonus6,bonus10,bonus;scanf(“%ld“,bonus1=100000*0.1;bonus2=bonus1+100000*0.75;bonus4=bonus2+200000*0.5;bonus6=bonus4+200000*0.3;bonus10=bonus6+400000*0.15;/*为方便后面的计算,先算出满 10 万、20 万、100 万时的提成*/switch(i/100000) /*整数相除的结果仍为整数 */case 0:
11、bonus=i*0.1; /*利润低于 10 万元*/case 1: bonus=bonus1+(i-100000)*0.075;break; /*利润在 10 万到 20 万之间*/ case 2: case 3: bonus=bonus2+(i-200000)*0.05; break; /*利润在 20 万到 40 万之间*/case 4: case 5: bonus=bonus4+(i-400000)*0.03; break; /*利润在 40 万到 60 万之间*/case 6: case 7: case 8: case 9: bonus=bonus6+(i-600000)*0.015
12、; break; /*利润在 60 万到 100 万之间*/default: bonus=bonus10+(i-1000000)*0.01; break; /*利润高于 100 万*/printf(“bonus=%d“,bonus);4.2 循环结构程序循环结构是程序中一种很重要的结构。其特点是,在给定条件成立时,反复执行某程序段,直到条件不成立为止。 给定的条件称为循环条件,反复执行的程序段称为循环体。 语言提供了多种循环语句,可以组成各种不同形式的循环结构。4.2.1 while 语句while 语句可以用于实现如图 4.5 所示的“当型”循环结构。其一般形式为: while (表达式)
13、语句while 语句的执行过程为:计算表达式的值,当值为真(非 0)时,执行循环体语句。其特点是:先判断,后执行。【例 4.10】 用 while 语句求阶乘 n!。main()int n;float fact=1;printf(“Input n:“);图 4.5 while 循环scanf(“%d“,while(n0)fact*=n-;printf(“n!=%1.0fn“, fact);本例程序中的循环条件为 n0。循环体 s*=n-相当于下列复合语句fact=fact*n;n-;但是执行效率更高。使用 while 语句应注意以下几点:(1)while 语句中的表达式一般是关系表达或逻辑表达
14、式,只要表达式的值为真(非 0)即可继续循环。(2)在语法上,循环体应该是一条语句。如需要包括有一个以上的语句,则必须用括起来, 组成复合语句。(3)在循环体中应包含使循环趋于结束的语句,以避免出现死循环。如上例中循环条件为 n0,因此在循环体中应该包含使 n 的值逐步变小的语句,如 s*=n-,n-等。(4)允许 while 语句的循环体又是 while 语句,从而形成循环的嵌套。4.2.2 do-while 语句do-while 语句用于实现如图 4.6 所示的循环结构,其特点是先执行循环体,然后再判断循环条件是否成立。其一般形式为:do语句while(表达式);do-while 语句的执
15、行过程为:先执行循环体语句一次, 再判别表达式的值,若为真(非 0)则继续循环,否则终止循环。do-while 语句和 while 语句的区别在于 do-while 是先执行后判断,因此 do-while 至少要执行一次循环体。而while 是先判断后执行,如果条件不满足,则一次循环体语句也不执行。【例 4.11】 用 do-while 语句求阶乘 n!。main()int n;float fact=1;printf(“Input n:“);scanf(“%d“,do fact *=n-; while(n0);printf(“n!=%1.0fn“, fact);将上例和例 4.10 进行比较可
16、以看出,while 语句和 do-while 语句在一般的应用中可以相互替换。但是,由于两条语句的执行过程不同,在某些条件下的结果会有所不同。例图 4.6 do-while 循环如以上两个求阶乘的程序,在执行时如果正常地从键盘输入一个大于 0 的值,两者的运行结果是一样的;如果从键盘输入一个小于或等于 0 的值,两者的运行结果就不一样了。读者可自己分析其中的差别。在使用 do-while 语句时,还应注意以下几点:(1)在 if 语句和 while 语句中, 表达式后面都不能加分号, 而在 do-while 语句的表达式后面则必须加分号,因为这是一条完整语句结束的标志。(2) 在 do 和 w
17、hile 之间的循环体由多个语句组成时,也必须用括起来组成一个复合语句。(3) do-while 和 while 语句相互替换时,要注意修改循环控制条件(4) do-while 语句也可以组成多重循环,而且也可以和 while 语句相互嵌套。4.2.3 for 语句for 语句是语言所提供的功能更强,使用更广泛的一种循环语句。其一般形式为:for (表达式 1;表达式 2;表达 3) 语句其中:表达式 1 一般是赋值表达式,用来给循环变量赋初值。如果在此之前循环变量已经被赋初值,则在for 语句中可以省略该表达式。表达式 2 一般为关系表达式或逻辑表达式,表示循环条件。表达式 3 一般是赋值语
18、句或自增(自减)语句,用来修改循环变量的值,表示循环变量的变化方式。for 语句的执行过程为(参见图 4.7):(1)首先计算表达式 1 的值。(2)再计算表达式 2 的值,若值为真(非 0)则执行循环体一次,否则跳出循环。(3)然后再计算表达式 3 的值,转回第 2 步重复执行。在整个 for 循环过程中,表达式 1 只计算一次,表达式 2 和表达式 3 则可能计算多次。循环体可能多次执行,也可能一次都不执行。【例 4.12】 用 for 语句求阶乘 n!。main()int n,i;float fact=1;printf(“Input n:“);scanf(“%d“,for(i=1;i0;
19、n-)fact *=n;printf(“n!=%1.0fn“, fact);在这里的 for 语句中,表达式 1 已省去,循环变量 n 的初值在 for 语句之前由 scanf语句取得。(2)如省去表达式 2,则不对循环条件进行判断,将造成无限循环。一般可以采用在循环体内设置转移语句 break 来跳出循环。例如,将例 4.12 的程序改为:main()int n;float fact=1;printf(“Input n:“);scanf(“%d“,for(;n-)if (n0;)fact *=n-;printf(“n!=%1.0fn“, fact);在循环体语句 fact *=n- 中,循环
20、变量 n 的值在使用后进行了自减,所以在 for 语句中的表达式 3 可以省略而不会形成死循环。(4)for 语句中的各表达式都可省略,但分号间隔符不能少。如:for(;表达式;表达式)省去了表达式 1;for(表达式;表达式)省去了表达式 2;for(表达式;表达式;)省去了表达式 3;for(;)省去了全部表达式。(5)循环体可以是空语句。例如:#include“stdio.h“main()int n=0;printf(“input a string:n“);for(;getchar()!=n;n+);printf(“%d“,n);本例中,省去了 for 语句的表达式 1,表达式 3 也不
21、是用来修改循环变量,而是用作输入字符的计数。这样, 就把本应在循环体中完成的计数放在表达式中完成了。因此循环体是空语句。应注意的是,空语句后的分号不可少,如缺少此分号,则把后面的 printf 语句当成循环体来执行。反过来说,如循环体不为空语句时, 决不能在表达式的括号后加分号, 这样又会认为循环体是空语句而不能反复执行。这些都是编程中常见的错误,要十分注意。(6)for 语句也可与 while,do-while 语句相互嵌套,构成多重循环。4.2.4 转移语句程序中的语句通常总是按顺序方向, 或按语句功能所定义的方向执行的。如果需要改变程序的正常流向, 可以使用本小节介绍的转移语句。在语言中
22、提供了 4 种转移语句:break, continue,return 和 goto。其中的 return 语句只能出现在被调函数中, 用于返回主调函数,我们将在函数一章中具体介绍。 本小节介绍其他三种转移语句。1break 语句break 语句用在 switch 语句或循环语句中, 其作用是跳出 switch 语句或跳出本层循环,转去执行后面的程序。break 语句的一般形式为:break;由于 break 语句的转移方向是明确的,所以不需要语句标号与之配合。我们在前面的介绍中已经了解到在 switch 语句中使用 break 语句的方法。下面我们看一个在循环语句中使用 break 语句的例子
23、。【例 4.13】 计算 s=1+2+3+100,若 s=1000,则跳出循环。main()int n=1,s=0;for (;n=1000) break;printf(“s=%dn“, s);使用 break 语句可以使循环语句有多个出口,在一些场合下使编程更加灵活、方便。 2continue 语句continue 语句只能用在循环体中,其作用是结束本次循环,即不再执行循环体中continue 语句之后的语句,转入下一次循环条件的判断与执行。其一般格式是:continue;应注意的是,continue 语句只结束本层本次循环体的执行,并不跳出循环。【例 4.14】 输出 100 以内能被 7
24、 整除的数。void main()int n;for(n=7;n0)fact *=n-;goto loop;printf(“n!=%.0fn“, fact);这里,通过 goto 语句和以语句标号 loop 标识的 if 语句配合使用,相当于一个循环结构功能。但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。4.2.5 循环的嵌套一个循环语句的循环体内包含另一个完整的循环语句,称为循环的嵌套。循环的嵌套可以组成多重嵌套。各种循环语句(while 语句、do-while 语句、for 语句)之间可以相互嵌套。例如,以下形式都是合法的嵌套
25、。(1)for()while()(2)dofor()while();(3)while()for()(4)for()for()4.2.6 程序举例【例 4.16】 利用公式 +求 的近似值,直到最后一项的绝对值71534小于 10-6 为止。对于循环次数未知的循环结构,一般不适合使用 for 语句实现,可以采用 while 语句或 do-while 语句来实现。用 while 语句实现的程序源代码如下:#includemain()int sign=1;float n=1,t=1,pi=0;while(fabs(t)1e-6)pi+=t;n+=2;sign*=-1;t=sign/n;pi*=4;p
26、rintf(“pi=%10.6fn“,pi);【例 4.17】 输出 100 以内的素数。所谓素数是指只能被 1 和其本身整除的数。算法分析:我们先用穷举法来对 2100 之间所有的数进行判断。在判断一个数 n 是否是素数时,我们对 n 用 2n-1 逐个去除,若某次可以整除则说明 n 不是素数,跳出该层循环。 如果在所有的数都是未除尽的情况下结束循环,则为素数。程序源代码如下:main()int n,i;for(n=2;n=n) printf(“t%d“,n);我们对此算法作进一步分析。实际上,2 以上的所有偶数均不是素数,因此可以使循环变量的步长值改为 2,即每次增加 2,此外只需对数 n
27、 用 2n/2 或 2 去除就可判断n该数是否素数。这样将大大减少循环次数,减少程序运行时间。#include“math.h“void main()int n,i,k;for(n=3;n=k) printf(“t%3d“,n);【例 4.18】 求 Fibonacci 数列的前 40 个数。算法分析:Fibonacci 数列起源于一个古典问题。有一对兔子,从出生后第 3 个月起每个月都生一对兔子,小兔子长到第 3 个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?经分析可知,兔子数的规律为数列 1,1,2,3,5,8,13,21,。该数列的前 2 个数为 1,1,从第 3 个
28、数开始,该数是其前面两个数之和。即f1=f2=1fn=fn-1+fn-2,n 3解题的算法见图 4.8。程序源代码如下:main()long f1,f2;int i;f1=f2=1;for(i=1;i=20;i+)printf(“%12ld %12ld“,f1,f2);if(i%2=0) printf(“n“); /*控制输出,每行四个*/f1=f1+f2; /*前两个月加起来赋值给第三个月*/f2=f1+f2; /*前两个月加起来赋值给第三个月*/【例 4.19】求解百鸡百钱问题。公元钱五世纪,我国古代数学家张丘建在算经一书中提出了“百鸡百钱”问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。
29、百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?算法分析:百鸡百钱问题是典型的穷举法问题。我们设 x,y,z 分别为买的鸡翁,鸡母,鸡雏的个数,则有x+y+z=1005*x+3*y+z/3=100而 x,y 可能取数的范围为x:020y:033z 可以由公式 z=100-x-y 计算出来。据此,我们建立一个两重循环,对所有可能的情况进行判别。程序算法见图 4.9,程序源代码如下:#include “stdio.h“main()int i,j,k;printf(“解百鸡百钱问题n“);for (i=1;i=20;i+)for (j=1;j=33;j+)图 4.8 求 Fibonacci 数列图 4.9 求解百鸡百钱问题k=100-(i+j);if (k%3=0