第5章循环程序设计 循环结构是一种重复执行的程序结构。它判断给定的条件,如果条件成立,则重复执行某一些语句(称为循环体),否则结束循环。通常,循环结构有“当型循环”(先判断条件,后执行循环)和“直到型循环”(先执行循环,再判断条件)。在C语言中,实现循环结构的语句主要有以下3种: (1) for 语句。 (2) while语句。 (3) dowhile语句。 5.1问题的提出 实际应用中的许多问题,都会涉及重复执行的操作步骤和相应的算法,如级数求和、方程的迭代求解、统计报表打印,等等。有时重复处理的次数是已知的,有时重复处理的次数是未知的。不管怎样,程序设计中都要用到循环结构。比如以下问题的求解过程都要用到循环结构。 (1) 计算1+2+3+…+n,这是一个循环累加的问题,每次循环累加一个自然数,总共需要n次加法运算,从而得到这个自然数数列之和。 (2) 计算n!=1×2×3×…×n,这是一个循环累乘的问题,每次循环乘一个自然数,总共需要n次循环,即累乘n个数,从而得到n的阶乘。 (3) 利用公式: π4=1-13+15-17+…,计算π的近似值,直到最后一项的绝对值小于10-6时认为满足精度要求。这是一个事先未知循环次数的问题,需要根据给定的条件来判断循环是否终止。 类似以上需要重复处理的问题,必须用循环语句来编写程序解决。其实循环语句不只在数学问题中发挥重要作用,联合使用选择语句和循环语句还可以设计出许多实用的程序。 循环结构和顺序结构、选择结构一起被称为结构化程序设计的3种基本结构。按照结构化程序设计的观点,任何可计算问题都可以用这3种基本结构来解决。 5.2while 语句 while语句用来实现“当型”循环结构。 while语句的一般形式为: while(表达式) 循环体语句; while语句的执行过程可以用如图51的传统流程图(a)与NS流程图(b)来表示。 图51while循环的流程图 其执行过程为: 先判断表达式,为真(非0)则执行语句,然后再判断。如果表达式为假(值为0)则跳过循环体而直接执行while语句的下一语句。因此循环体可能一次也没有执行。当初始条件为假时,是不会执行循环的。 注意: (1) 如果循环体包含两条及以上的语句,应用{}括起来,构成一个复合语句,否则系统只把第一个语句当成循环体部分加以重复执行,余者作为while循环的后续语句。 (2) 循环体中应包含改变循环条件的语句,否则可能导致死循环。 【例5.1】求1+3+…+99的值。 首先画出流程图如图52所示。 图521+3…+99的while循环流程图 #include <stdio.h> void main( ) {int i,sum; i=1; sum=0; while(i<=99) {sum=sum+i; i=i+2; } printf("sum=%d\n",sum); } 当然,本题也可用其他算法完成,如转换成 ∑50i=12i-1的形式。 【例5.2】从键盘读入一系列字符,以#结束,统计字符的个数。 程序分析: 这是典型的标志法。以#作为标志,当此标志出现就结束循环。由于有可能第一个字符就是#,因此适合用当型循环完成。此外,还需要一个计数器,用来统计实际字符个数。 程序流程图如图53所示。 #include <stdio.h> void main( ) {int count; char ch; count=0; scanf("%c",&ch); while(ch!= '#') {count++; scanf("%c",&ch); } printf("total=%d\n",count); } 图53例5.2的两种流程图表示 5.3dowhile 语句 dowhile语句是另一种用来实现“当型”循环的结构。与while循环不一样,它是先执行,后判断。 图 54dowhile 循环的流程图 它的一般形式为: do 循环体语句 while (表达式); dowhile语句的执行过程可以用54的流程图表示。 其执行过程为: 先执行循环体语句,然后判断表达式,如果表达式值为真,则重复执行循环体,如表达式值为假,则结束循环。因此dowhile 的循环体语句至少会执行一次。 【例5.3】用dowhile 循环求1+3+…+99的值。 其流程图与例5.1类似,只是条件判断放在循环体之后,如图55所示。 图 55用dowhile 流程图表示1+3+…+99 #include <stdio.h> void main( ) {int sum,i=1; sum=0; do sum=sum+i; i=i+2; while (i<=99); printf("sum=%d\n",sum); } 说明: 当dowhile 之间的循环体由多个语句构成时,可以用{}括起来,也可不用,系统会自动把它们都当作循环体语句处理。 【例5.4】从键盘输入某班级学生的英语考试成绩,编程计算总分和平均分。 程序分析: (1)由于学生人数未知,也只有采用标志法。由此设定: 在最后一个学生成绩的后面添加一个标志-1,因为学生成绩一般情况下是≥0的。 (2) 由于班级中学生人数>0,故可使用dowhile循环。 程序流程图如图56所示。 #include <stdio.h> void main( ) {int count,score; float total,aver; total=0;count=0; printf("input scores:\n"); scanf("%d",&score); do total=total+score; count++; scanf("%d",&score); while(score!=-1); aver=total/count; printf("count=%d total=%7.2f aver=%5.2f\n",count,total,aver); } 图56例5.4 的流程图 5.4for 语句 for循环是C语言中使用最频繁也是最灵活的一种语句,它主要适用于循环次数已知的情况,但也可用于循环次数未知的情况。 for语句的一般形式为: for(表达式1;表达式2;表达式3) 循环体语句; 它的执行过程可以用如图57所示的流程图表示。 for 循环的执行过程为: (1) 求表达式1的值; (2) 判断表达式2; (3) 若值为真,则执行循环体语句,并执行表达式3,重复步骤(2); (4) 若表达式2的值为假,则结束循环,执行for语句的后续语句。 for循环的执行可以理解为如下的形式: for(循环变量赋初值;循环条件;循环变量自增值) 循环体语句; 【例5.5】用for循环求1+3+…+99的值。 #include <stdio.h> void main( ) {int i,sum=0; for(i=1;i<=99;i=i+2) sum=sum+i; printf("sum=%d\n",sum); } 【例5.6】从键盘输入10个数,找出其中的最小值。流程图如图58所示。 图 57for 循环的流程图表示 图58例 5.6的NS流程图 #include <stdio.h> void main( ) {int i,x,min; printf("input 10 datas:\n"); scanf("%d″,&x); min=x; for(i=2;i<=10;i++) {scanf("%d",&x); if(x<min) min=x; } pritnf("min=%d\n",min); } 关于for循环使用的具体过程中,有几点值得说明的地方: (1) 表达式1、表达式3可以是简单的表达式或逗号表达式,表达式2一般是关系表达式或逻辑表达式(也可以是其他表达式,值为0表示假,非0表示真)。 上述例5.5可以表示为如下形式: for(sum=0,i=1;i<=99;i=i+2 ) sum=sum+i; 或 for(sum=0,i=1;i<=99;i++,i++) sum=sum+i; 例如,统计键盘输入字符个数可以用以下for语句表示: for(i=0;(ch=getchar())!='\n';i++); (2) 表达式可以省略,但分号不能省。 ① 表达式1可以省略,但应在for语句之前给循环变量赋初值,如上例可以改为: sum=0;i=1; for(;i<=99;i=i+2) sum=sum+i; ② 表达式2可以省略,执行时就没有循环条件可判断,循环无休止地执行下去,如: for(i=1;;i=i+2) sum=sum+i; 就构成死循环。但可以采取其他办法避免出现这种情况,如在循环体中使用 goto 语句或break语句等。 ③ 表达式3可以省略,但应在循环体中添加循环变量自增的语句,否则也会造成死循环。如上例也可表示为: for(i=1;i<=99;) {sum=sum+i; i=i+2; } ④ 可以同时省略表达式1和表达式3,只有表达式2,例: i=1; for(;i<=99;) { sum=sum+i; i=i+2; } 此时相当于 while 语句。实际上,for 循环完全可以替代while循环,但其用法远比while灵活。 ⑤ 3个表达式均可同时省略,此时情况与②类似,也要采取其他办法来控制循环结束。 5.5goto、break、continue语句 1. goto 语句 有时需要从程序中的某个语句转移到另一个语句,这时可以使用goto语句。goto语句是无条件转移语句,它的一般形式为: goto语句标号; 其中语句标号为标识符,它的命名规则与变量名一样,只能由字母、数字、下画线组成,且只能由字母或下画线开头。例如: goto loop; 表示将流程无条件地转移到loop所标识的语句去继续执行。 goto语句一般与if 语句配套使用,用来构成循环,或者从循环体内跳转到循环体外。 【例5.7】用 goto语句求1+3+…+99。 #include <stdio.h> void main( ) {int i,sum=0; i=1; next: sum=sum+i; i=i+2; if (i<=99) goto next; printf"sum=%d\n",sum); } 结构化程序不提倡使用goto 语句,因为频繁地使用 goto 语句使得程序结构毫无规律可言,如同一团乱麻。 2. break语句 break语句不仅能跳出switch语句,而且能跳转出任何一种循环语句的循环体,进而执行循环语句的下一个语句。 break语句的一般形式为: break; 【例5.8】用break语句完成例5.7。 #include <stdio.h> void main( ) {int i,sum; for(sum=0,i=1;;i=i+2) {sum=sum+i; if(i>=99) break; } printf("sum=%d\n",sum); } 注意: (1) break语句一般与if 语句配套使用,用来控制是否继续循环。 (2) break语句只能用于switch语句和循环语句中,不能用于任何其他语句。 3. continue语句 continue语句用于结束本次循环,即跳过循环体中尚未执行的语句,流程转移到判断循环条件处,准备下一次循环。 continue语句的一般形式为: continue; 【例5.9】从键盘输入10个整数,打印所有的负数。 #include <stdio.h> void main( ) {int x,i; printf("input 10 datas:\n"); for(i=1;i<=10;i++) {scanf("%d",&x); if(x>=0) continue;/* 非负数就跳过 */ printf("%8d",x); } } 下面举例来形象地区分二者。假设有以下两种结构的循环: (1) while(表达式1) {︙ if(表达式2) break; ︙ } (2) while(表达式1) {︙ if(表达式2) continue; ︙ } 图59是它们的流程图,请注意break与continue的区别。 图59break与continue的区别 5.6循环的嵌套 如果在一个循环内完整地包含另一个循环结构,则称为多重循环或循环嵌套。嵌套的层数可以根据需要而定,嵌套一层称为二重循环,嵌套二层称为三重循环,以此类推。 3种循环语句(while循环、dowhile循环和for循环)可以相互嵌套,下面是几种常见的二重嵌套形式。 ① for(…) {… for(…) { … } } ② for(…) {… while(…) { … } } ③ while(…) { … for(…) { … } } ④ while(…) { … while(…) { … } } ⑤ do {… for(…) { … } }while(…); ⑥ do {… do { … }while(…); … }while(…); 【例5.10】输出由数字组成的如下所示的金字塔图案。 1 222 33333 4444444 555555555 66666666666 7777777777777 888888888888888 99999999999999999 分析: 输出图案一般可由多重循环实现,外循环来控制输出的行数,内循环控制每行的空格数和字符个数。程序如下: #include<stdio.h> void main( ) { int i, k, j; for (i = 1; i <= 9; i++)//外循环控制输出行数 { for (k = 1; k <= 10 - i; k++) //每行起始输出位置 { printf(" "); //输出空格符 } for (j = 1;j <= 2 * i - 1; j++) //内循环控制输出字符个数 { printf("%c",'0' + i); //输出内容 } printf("\n"); //换行 } } 图510求1!+2!+…+10! 循环可以嵌套使用,即循环体内还可以包含另一个完整的循环。循环的嵌套可以是二重的,也可以是多重的。 循环的嵌套形式是多种多样的,前面介绍的几种循环语句,都可以互相嵌套。例如在while的循环体中包含一个for循环,或是for 循环中包含一个dowhile循环等。 【例5.11】求1!+2!+…+10!。 #include <stdio.h> void main( ) {int i,j; long mul,sum=0; for(i=1;i<=10;i++) {mul=1; for(j=1;j<=i;j++) mul=mul*j; sum=sum+mul; } printf("sum=%ld",sum); } 使用循环嵌套时,要注意几个问题: (1) 外层循环必须完全嵌套内层循环,严禁交叉嵌套; (2) 内、外层循环变量尽量不要同名,否则结果不可预料。 例如: for(i=1;i<=10;i++) for(i=1;i<=10;i++) printf("*"); 一共打印了多少个*?请大家思考。 5.73种循环语句比较 一般情况下,3种循环语句可以相互代替,表51列出了各种循环语句的区别。 表51几种循环语句的区别 格式for(表达式1; 表达式2; 表达式3){循环体;}while(表达式) {循环体;}do{循环体;} while(表达式); 循环类别当型循环当型循环直到型循环 循环变量初值一般在表达式1中在while之前在do之前 循环控制条件表达式2的值表达式的值表达式的值 提前结束循环breakbreakbreak 改变循环条件一般在表达式3循环体中用专门语句循环体中用专门语句 说明: (1) 3种循环中for语句功能最强大,使用最多,任何情况的循环都可使用for语句实现。 (2) 当循环体至少执行一次时,使用dowhile语句与while语句等价。如果循环体可能一次也不执行,则只能使用while语句或for语句。 5.8程序举例 许多程序都要用到循环结构,关于循环的算法很多,这里只列举一些常用的算法。 【例5.12】输入两个正整数m和n,求它们的最大公约数。 程序分析: 求最大公约数可以用“辗转相除法”: 将大数m作为被除数,小数n作为除数,二者余数为r。如果r≠0,则将 n→m,r→n,重复上述除法,直到r=0 为止。此时最大公约数就是n,流程图如图511所示。 图511求最大公约数 #include <stdio.h> void main( ) {int m,n,r,t; printf("input m and n:\n"); scanf("%d%d",&m,&n); if(m<n) { t=m;m=n;n=t;} r=m%n; while (r!=0) {m=n; n=r; r=m%n; } printf("%d",n); } 【例5.13】打印Fibonacci数列的前20项,每行打印5个数。该数列前两个数是“1、1”,以后的每个数都是其前两个数之和。 程序分析: 这要用到递推法。所谓递推,是指根据前面的一个或多个结果推导出下一个结果。这里设3个变量f1、f2、f3,其中f3=f1+f2。流程图如图512所示。 图512输出20项Fibonacci数列 #include <stdio.h> void main( ) {int f1,f2,f3,i; f1=1;f2=1; printf("%10d%10d",f1,f2); for(i=3;i<=20;i++)/* 求后面18个数 */ {f3=f1+f2; printf("%10d",f3); if(i%5==0) printf("\n"); f1=f2; f2=f3; } } 【例5.14】打印出所有的“水仙花”数。所谓“水仙花”数,就是一个3位数,其各位数字的立方和等于该数本身。例如407就是一个“水仙花”数,因为 407=43+03+73。 程序分析: 本题可以采用穷举法。穷举法就是把所有的可能组合一一考虑到,对每种组合都判断是否符合要求,符合则输出。 #include <stdio.h> void main( ) {int i,j,k,m,n; for(i=1;i<=9;i++) for(j=0;j<=9;j++) for(k=0;k<=9;k++) {m=i*100+j*10+k; n=i*i*i+j*j*j+k*k*k; if(m==n) printf("%10d",m); } } 【例5.15】从键盘输入一行字符,要求将所有大写字母转换成小写,小写字母转换成大写,然后输出该字符串。 #include <stdio.h> void main( ) {char ch; while((ch=getchar()!='\n') {if(ch>='a' && ch<='z') ch=ch-32; /*小写变大写*/ else if(ch>='A' && ch<='Z' ) ch=ch+32; /* 大写变小写 */ putchar(ch); } } 如果上例去掉else,即改成 if(ch>='a' && ch<='z') ch=ch-32; if(ch>='A' && ch<='Z') ch=ch+32; 后,会得到什么结果? 【例5.16】输入一正整数n,在屏幕中央打印n行三角形。如n=4,则打印: * *** ***** ******* 程序分析: 要想将图形打印在屏幕中央,应在每行输出第一个* 之前打印若干个空格作为占位符。这里假设图形输出在10列处。 #include <stdio.h> void main( ) {int i,j,n; printf("input n:\n"); scanf("%d",&n); for(i=1;i<=n;i++) {for(j=1;j<=10;j++) printf(" "); /* 打印10个空格,占位 */ for(j=1;j<=2*i-1;j++) printf("*"); /* 打印 *号 */ printf("\n"); /* 打印完一行,换行 */ } } 【例5.17】编一个程序验证哥德巴赫猜想: 一个大于等于6的偶数可表示为两个素数的和。 例如: 6=3+3,8=3+5,10=3+7。 程序分析: 设n为大于等于6的任一偶数,将其分解为n1和n2两个数,使得n1+n2=n,分别判断n1和n2是否为素数,若都是素数,则为一组解。若n1不是素数,就不必再检查n2是否为素数。从n1=3开始判断,直到n1=n/2为止。事实上,在判断一个数m是否是素数时,可以减少循环次数,因为m=sqrt(m)*sqrt(m),所以,当m能被大于sqrt(m)的整数整除时,在2~ sqrt(m)之间,至少存在一个能整除m的数,因此只要判断m能否被2,3,…,sqrt(m)整除即可。 程序代码如下: #include<stdio.h> #include<math.h> void main( ) { int n, n1, n2, j, k; printf("Enter a number n=?\n"); scanf("%d", &n); for (n1 = 3; n1 <= n / 2; n1++) { k = sqrt(n1); //求n1的平方根 for (j = 2; j <= k; j++) { if (n1 % j == 0) { break; } } if (j < k) { continue; } n2 = n - n1; k = sqrt(n2); for (j = 2; j <= k; j++) { if (n2 % j == 0) { break; } } if (j > k) { printf("%d=%d+%d\n",n,n1,n2); } } } 输入及程序运行过程: Enter a number n=? 64 64=3+61 64=5+59 64=11+53 64=17+47 64=23+41 习题5 1. 输入一行字符,分别统计出其中字母、数字和其他字符的个数。 2. 编程求 1-12+13-14+…+199-1100。 3. 编写程序,对数据进行加密。从键盘输入一个数,对每一位数字均加2,若加2后大于9,则取其除10的余数。如,2863加密后得到4085。 4. 输出3~100之间的所有素数。 5. 验证2000以内的哥德巴赫猜想: 对于任何一个大于4的偶数,均可以分解为两个素数之和。 6. 求11×2+12×3+…+1n×(n+1),直到某一项小于0.001时为止。 7. 100匹马驮100担货,大马一匹驮3担,中马一匹驮2担,小马两匹驮1担,求大马、中马、小马的数目,要求列出所有的可能。 8. 假设我国国民经济总值按每年8%的比率增长,问几年后翻番。 9. 从键盘上输入10个整数,求其中的最大值和次大值。 10. 从键盘输入n,输出n行*组成的倒等腰三角形,如n=4,则输出: ******* ***** *** * 11. 用迭代法求X=a。迭代公式为: Xn+1=12Xn+aXn,要求迭代精度满足|Xn+1-Xn|<0.00001。 12. 求解爱因斯坦数学题。有一条长阶梯,若每步跨2阶,则最后剩1阶; 若每步跨3阶,则最后剩2阶; 若每步跨5阶,则最后剩4阶; 若每步跨6阶,则最后剩5阶; 若每步跨7阶,最后一阶都不剩,问总共有多少级阶梯? 13. 打印如下的九九乘法表: 123456789 1 24 369 481216 510152025 91827364554637281