第5章 CHAPTER 5 循环结构程序设计 现实生活中常常会遇到需要重复处理的问题,在这一类问题中有一些步骤是被重复执行的,反映到程序中表现为一组指令或程序段被有条件地反复执行,这种不断被重复执行的结构称为循环结构。循环结构是结构化程序设计的基本结构之一,它的特点是在给定条件成立时,反复执行某程序段,直到条件不成立时停止。其中给定的条件称为循环条件,反复执行的程序段称为循环体。 C语言中提供了goto语句、while语句、do while语句和for语句实现循环结构,它们可以用来处理同一问题,一般情况下可以相互替换。但是结构化的程序设计不提倡使用goto语句,因为它强制改变程序的执行顺序,会给程序的运行带来不可预料的错误。本章主要学习while、do while和for这3种循环控制语句。 本章学习重点: (1) 循环的概念及特点; (2) while、do while和for语句的语法格式及应用; (3) break语句和continue语句的语法格式及应用; (4) 循环嵌套的应用。 本章学习目标: (1) 理解循环的概念及特点; (2) 掌握while、do while、for语句的语法格式及使用方法; (3) 掌握continue语句和break语句在循环中的应用; (4) 灵活运用3种循环语句及循环嵌套解决实际问题。 5.1循环结构的引入 如果需要输出1~10的10个整数,该如何解决?最直接的方法是用printf()函数输出这10个数。 printf("%d%d%d%d%d%d%d%d%d%d ",1,2,3,4,5,6,7,8,9,10); 但这样书写比较麻烦,如果要求输出更多的数,如1~100、1~1000,这种方法显然不合适,可以尝试使用另一种方法。 printf("%d ",1); printf("%d ",2); … printf("%d ",10); 在上面的方法中10条语句基本类似,后面的输出项由1到10,每次递增1,呈规律性变化。如果用变量i表示输出项,用i=i+1实现递增,可将程序改写为 int i=1; printf("%d ",i);i=i+1; … printf("%d ",i);i=i+1;10条语句 通过观察发现,“printf("%d ",i);”和“i=i+1;”这两条语句是重复执行的,可以将这两条语句作为循环体反复执行。如果输出1~10,则让它们重复执行10次; 如果输出1~100,则重复执行100次。那么如何实现重复执行呢?可以借助C语言提供的循环控制语句来实现。 5.2while语句 在C语言中,可以使用while语句来实现循环,它的特点是通过判断循环控制条件是否满足来决定是否执行循环,一般形式为 while (表达式) { 语句序列 } 执行流程: 先计算表达式的值,如果表达式的值是非0值(逻辑真),表示循环条件成立,则执行语句序列。并再次计算表达式的值,如果其值仍是非0值(逻辑真),则继续执行语句序列。此过程反复执行,直到表达式的值为0(逻辑假)时循环结束。while语句执行流程图如图51所示。 图51while语句执行流程图 说明: (1) 表达式为循环控制条件,可以是任何类型的表达式,一般情况是关系表达式或逻辑表达式。只要表达式的值非0(逻辑真),则循环条件成立,执行语句序列。 (2) 语句序列是循环体,可以是一条语句,也可以是多条语句或空语句。如果是多条语句,要加花括号构成复合语句。 利用while循环语句解决前面的问题: 输出整数1~10,其算法流程图如图52所示。 图52输出10个整数流程图 程序如下: int main() { i=1; while (i<=10) { printf("%d\n",i); i=i+1; } return 0; } 使用while语句应注意的问题: ① 给循环变量赋初值。在程序中用来控制循环条件的变量称为循环变量。进入循环前,必须给循环变量赋初值。因为C语言不会自动给变量赋初值(除非是全局变量或静态局部变量,系统会为它们自动赋初值为0),如果没有给循环变量赋初值,则可能会得到一个该存储空间上一次运算的遗留值,所以导致程序运行的结果不能确定。 例如, int i; while (i<=10) { printf("%d\n",i); i=i+1; } 运行结果为 -858993460 -858993461 -858993462 … 由于在进入循环体前没有给循环变量i赋初值,因此循环变量i获得了一个随机值-858993460,而且这个值满足循环条件i<=10,所以执行循环体并输出i值,然后自增1后继续循环。 思考: 观察下面的程序段,分析程序运行结果。 int i; while (i<=10) { i=1; printf("%d\n",i); i=i+1; } ② 循环的执行次数是由循环条件决定的。表示循环条件的表达式可以是任意类型的表达式,也可以是常量或变量,只要表达式的值非0(逻辑真),表示循环条件成立,就执行循环体,否则就不执行循环体。如果表达式的值一开始就为0(逻辑假),那循环体一次也不执行。 例如,已有定义“int i=10,a=1;”,则下面的3个循环条件均为真,都能执行相应的循环体。 while (i<=100){… } while (i<=100&&i>=10){…} while (a) {… } 而下面的循环语句中因为一开始循环条件就为假,所以循环体一次也没有执行。 while (i>=50){… } //i的初值为10,所以表达式的值为0 ③ 在循环控制表达式中,或在循环体内必须有改变循环变量值的语句,使循环趋向结束,否则会形成死循环。所谓死循环,是指无法靠自身的控制终止的循环。例如, int i; i=1; while (i<=100) { printf("%d\n",i); } 由于在循环体内没有改变循环变量i的表达式,所以i的值一直维持1,形成死循环。 【例51】求∑100i=1i。 解题思路: (1) 根据题目可知s=1+2+3+…+100,这是一个累加求和问题,先后有100个数相加,加法运算要执行100次,所以用循环结构解决。 (2) 反复执行的操作是两个数相加,其中加数总是比前一个加数增加1,如果用变量i来表示加数,则i=i+1。第一个加数是1,最后一个加数是100,所以i值从1增长到100后就不再循环。 图53求∑100i=1i的NS流程图 (3) 被加数是上一次加法运算的和,如果用变量s存放上一次相加的和,那么加法运算可以写成s+i,然后再将加法运算的结果存入s,因此就有表达式s=s+i。这里s表示累加和,通常称为累加器。需要注意,累加器s的初值一定要设为0,否则会因为s的初值不确定,导致程序运算结果出错。 算法流程图如图53所示。 程序如下: #include<stdio.h> int main() { int i=1; //定义循环变量i,初值为1 int s=0; //定义累加器s,初值为0 while (i<=100) //循环执行条件i<=100 { s=s+i; //累加器求和 i=i+1; //加数自增1,逐渐向循环跳出条件i>100靠拢 } printf("s=%d,i=%d\n",s,i); //输出累加和,以及跳出循环时的i值 return 0; } 运行结果为 s=5050,i=101 思考: 如何求1~100的奇数和?如何求1~100的偶数和?求1~100中的5的倍数和? 【例52】 用公式π4=1-13+15-17+19-…求π的近似值,直到最后一项的近似值小于10-4为止。 解题思路: 根据题目可知,这个问题也是累加求和问题,不同之处在于: (1) 公式中每个分数的分母为奇数1,3,5,7,9…,每循环一次增长值为2。如果用变量n表示分母,则n=n+2,n的初值为1。 (2) 每个加数的符号是正负交替变化的,可以定义一个专门用于记录符号变化的变量flag,每循环一次,flag=(-1)*flag,实现正负符号交替变化。 (3) 如果每一个加数(即1/n)用变量t来表示,虽然不知道要加多少项,但可以用最后一项t的绝对值小于10-4来作为循环结束的条件。所以循环的执行条件可以表示为|t|≥10-4。 程序如下: #include<stdio.h> #include<math.h> //调用fabs()函数需要包含math.h文件 int main() { int flag=1; //定义变量flag初值为1 float n=1.0; //定义变量n表示分母,初值为1.0 float s=0.0; //定义累加器s,初值为0 float t=1.0; //定义变量t表示每一个分数,初值为1 while (fabs(t)>=1E-4) //当加数t≥10-4时执行循环 { s=s+t; n=n+2; flag=-flag; //符号正负交替 t=flag*(1/n); //求出下一次累加的分数项 } s=s*4.0; //求π值 printf("pi=%f\n",s); return 0; } 运行结果为: pi=3.141397 5.3do while语句 do while语句可以实现直到型循环,一般形式为 do { 语句序列 }while ( 循环控制表达式 ); 执行过程: 首先无条件地执行一次循环体,然后计算表达式的值,如果表达式的值非0(逻辑真),表示循环条件成立,则返回重新执行循环体; 直到表达式的值为0(逻辑假),循环条件不成立时结束循环。所以,do while循环的特点是无论如何都会执行一次循环体。do while语句执行流程图如图54所示。 图54do while语句执行流程图 说明: (1) do相当于一个标号,它标志着循环结构的开始,注意后面不能加分号“;”。 (2) 语句序列无论是一条语句,还是多条语句,都用花括号{ }将它括起来,使得结构清晰,尤其对于初学者来说更容易理解。 (3) do while语句整体上是一条语句,所以while(表达式)后面的语句结束标志分号“;”不能缺少。 图55求∑100i=1i的NS流程图 用do while语句改写【例51】,算法流程图如图55所示。 循环部分的代码如下: i=1; do//循环开始,后面不能跟分号";" { s=s+i; i=i+1; }while(i<=100); //语句后面的分号";"不能少 注意: while语句和do while 语句处理循环时,它们的区别主要体现在以下两个方面: ① 书写格式上不同。while语句中while(表达式)后面没有分号“;”,而do while语句在while(表达式)后面一定要有分号“;”。 ② 执行流程不同。while语句是先判断条件然后再执行循环体,而do while语句是先执行循环体,然后再判断条件。如果循环条件一开始就不满足,那么while循环一次也不执行,而do while循环会执行一次。 思考: 观察下面两段代码,分析运行结果。 程序(a) i=10; while (i<1) { printf("%d",i); i=i+1; } 程序(b) i=10; do { printf("%d",i); i=i+1; }while (i<1); 【例53】输入两个数m和n,求m和n的最大公约数。 解题思路: 辗转相除法求最大公约数是用m除以n求余数r,当r≠0 时,用除数做被除数,用余数做除数再求余数r,如此反复,直到r=0 时,除数即为所求的最大公约数。算法流程图如图56所示。 图56用do while求最大公 约数的NS流程图 程序如下: #include<stdio.h> int main() { int m,n,r; scanf("%d%d",&m,&n); do { r=m%n; m=n; //将除数n赋给被除数m n=r; //将余数r赋给除数n }while (r!=0); //直到r=0跳出循环 printf("最大公约数是%d\n",m); return 0; } 运行结果为 24 10↙ 最大公约数是: 2 5.4for语句 C语言提供了另一个使用非常广泛的语句for循环语句,通常适用于已知循环次数的情况,一般形式为 for(表达式1; 表达式2; 表达式3) { 语句序列 } 执行过程: 先执行表达式1,然后判断表达式2的循环条件是否为真。如果为真则执行循环体,然后再执行表达式3,接下来继续判断表达式2的循环条件是否为真,如果为真则继续执行循环体和表达式3,直到表达式2表示的循环条件为假时结束循环,执行for语句后面的语句。for语句执行流程如图57所示。 图57for语句执行流程图 说明: (1) 表达式1一般是赋值表达式,它的作用是为循环变量赋初值。表达式1决定了循环的起始条件,只在循环开始前执行一次。 (2) 表达式2是循环控制条件,用来判断是否继续执行循环,一般是关系表达式或逻辑表达式。每次执行循环体前先计算表达式2的值,只要它的值是非0值,表示循环条件成立,执行循环体,否则结束循环执行for循环后面的语句。 (3) 表达式3一般为赋值表达式,它的作用是改变循环变量的值,使循环趋向结束,通过表达式3定义每执行一次循环后循环变量将如何变化。 (4) 循环体中语句序列可以是一条语句,也可以是多条语句,通常用花括号括起来。 (5) 通过比较for循环和while循环的流程图,发现它们的流程都是一样的,只是写法有所不同,所以while循环与for循环可以相互转换。相比之下,for语句的结构更加紧凑、清晰。for循环相当于下面的while循环。 表达式1; //给循环变量赋初值 while (表达式2) //表达式2为循环判断条件 { 语句序列; 表达式3; //改变循环变量的值 } 用for语句改写【例51】,循环部分代码如下: for (i=1; i<=100; i++) { s=s+i; } 【例54】编写程序,输出斐波那契数列的前20项,要求每行输出10个数。 解题思路: 斐波那契数列是这样一个数列: 1、1、2、3、5、8、13、21,规律是从第3个数开始后面的每一个数都是前面两个数的和。 斐波那契数列可以用数学上的递推公式来表示: F1=1 F2=1 Fn=Fn-1+Fn-2 算法流程如图58所示。 图58斐波那契数列NS流程图 程序如下: #include<stdio.h> int main() { int f1,f2,f,i; f1=1,f2=1; printf("%6d%6d",f1,f2) ; //输出f1,f2并控制每项占6列 for (i=3;i<=20;i++) { f=f1+f2; //从第3项开始,每项都是前两项的和 printf("%6d",f); f1=f2; f2=f; if (i%10==0) printf("\n") ; //控制每行输出10个数 } return 0; } 运行结果为 11235813213455 891442333776109871597258441816765 【例55】韩信有一队兵,他想知道有多少人,便让士兵报数: 按从1~5报数,最末一个士兵报的数为1; 按从1~6报数,最末一个士兵报的数为5; 按从1~7报数,最末一个士兵报的数为4; 按1~11报数,最末一个士兵报的数为10。编程求韩信至少有多少个士兵。 解题思路: 本题采用穷举法,它的基本思想是假设各种可能的解,让计算机逐一进行测试,如果测试的结果满足条件,则假设的解就是所要求解的值。从1开始逐个取出一个自然数进行判断,如果有一个数除以5、6、7、11后的余数分别是1、5、4、10,那么这个数就是要求的解,此时结束测试并输出这个数。如果没有满足条件,则继续测试下一个数,直到找到该数。 程序如下: #include<stdio.h> int main() { int n; for (n=1; ;n++) //士兵人数从1开始 { if (n%5==1&&n%6==5&&n%7==4&&n%11==10) //4个条件都满足时结束循环 { printf("士兵至少有%d人\n",n); //输出人数n break; //跳出循环 } } return 0; } 运行结果为 士兵至少有2111人 使用for语句时应注意: ① for语句中表达式1可以省略,但其后的分号“;”不能省略。此时需要在for语句之前给循环变量赋初值。 例如, i=1; for (; i<=100;i++) s=s+i; ② for语句中表达式2也可以省略,但其后的分号“;”不能省略。如果表达式2省略,则默认循环条件恒为“真”,循环将一直执行下去成为死循环,除非循环体内有控制循环退出的语句。 例如, for (i=1; ;i++) //若省略表达式2,则循环条件恒为真 { s=s+i; if (i>100) break; //如果i>100,用break跳出循环 } ③ for语句中表达式3也可以省略,此时需要在循环体内增加改变循环变量值的表达式,否则会形成死循环。 例如, for (i=1; i<=100; ) { s=s+i; i++; //改变循环变量i的值,使循环趋向结束 } ④ 如果同时省略表达式1和表达式3,只有表达式2,则需要在for语句前给循环变量赋初值,在循环体内改变循环变量的值,此时相当于只给了循环条件,与while循环完全相同。 例如, i=1; i=1; for (;i<=100; ) while (i<=100) { { sum=sum+i;等价于 sum=sum+i; i++; i++; } } ⑤ 如果表达式1、表达式2、表达式3都省略,那么此时没有循环条件,默认循环条件恒为“真”,既没有给循环变量赋初值,也没有改变循环变量的值,则会无休止地执行循环体,形成死循环。 例如, for (; ;){… } 等价于: while (1){… } ⑥ 表达式1和表达式3既可以是赋值表达式,也可以是逗号表达式。如果是逗号表达式,则可以在给循环变量赋初值的同时,给其他变量赋值; 或是在改变循环变量的同时,给其他变量赋值。 例如, int i,j; for (i=1,j=10;i<=j;i++,j--) //i,j初值都为1,每循环一次i递增,j递减,直到i>j时跳出循环 printf("i=%d,j=%d\n",i,j); ⑦ 循环体也可以是空语句。 例如, for (i=1,s=0;i<=100;i++,s+=i); //循环体为空 5.5循环嵌套 循环嵌套是指在一个循环体内又包含另一个完整的循环结构。嵌套在循环体内的循环称为内层循环,外面的循环称为外层循环。内嵌的循环中还可以嵌套循环,形成多层循环,while循环、do while循环和for循环可以互相嵌套。例如,下面几种都是合法的形式: (1) while () {︙ while () {…} } (2) do {︙ do { … }while (); }while (); (3) for ( ; ; ) {︙ for ( ; ; ) {…} ︙ } (4) for ( ; ; ) {︙ while () {…} ︙ } 【例56】编程输出如图59所示的九九乘法口诀表。 图59九九乘法口诀表 解题思路: (1) 观察乘法表中第1行的变化规律: 被乘数1保持不变,乘数从1变化到9,每次增加1,因此构造如下循环即可实现乘法表第1行的输出。 图510乘法口诀表NS流程图 for (j=1; j<=9; j++) printf("%1d*%1d=%2d ", 1*j); (2) 再观察乘法表中第2行的变化规律: 被乘数2保持不变,乘数从1到9每次递增1,与第1行的处理过程一样,只需将被乘数改为2,然后将上面的循环再执行一次即可。 (3) 同理,第3行、第4行、……、第9行,处理过程都一样,只需将被乘数从1变化到9,对上面循环执行9次。因此在上面循环的外面再加上一个循环构成双重循环,就可以输出九九乘法口诀表。算法流程图如图510所示。 程序如下: #include<stdio.h> int main() { int i, j; for (i=1; i<=9; i++)//外层循环变量i,控制被乘数变化 { for (j=1; j<=9; j++) //内层循环变量j,控制乘数变化 printf("%1d*%1d=%2d ", i, j, i*j) ; //输出格式控制 printf("\n");//输出一行后换行 } printf("\n"); return 0; } 双重循环的执行过程是: 首先执行外层循环,当外层循环变量i取初值1时执行内层循环,内层循环变量j的值将从1变化到9,在这个过程中i值始终不变,直到内层循环执行完毕。外层循环i的值才增长为2,然后再执行一遍内层循环,j值从1变化到9。如此反复执行下去,直到外层循环变量i的值超过终值9,整个双重循环才执行完毕。可以看出,在双重循环的执行过程中,外层循环执行一次,内层循环就要执行一遍。 思考: 修改上面的程序,输出如图511所示的乘法口诀表。 图511乘法口诀表示意图 【例57】求100~200的全部素数。 解题思路: (1) 如果一个整数只能被1和它本身整除,那么这个数(除了整数1)就是素数。如2、3、5、7是素数,而4、6不是素数。 (2) 判断某数m是素数的方法,用 2,3,4,……,m-1逐个去除m,看它能否被整除。如果m能被其中一个数整除,那么m就不是素数,如果m不能被其中任一个数整除,那么m就是素数。当m较大时,除法执行的次数过多降低了程序的执行效率,所以可以对算法进行优化,只需用m除以2,3,……,m就可以了。 (3) 因为偶数一定不是素数可以直接排除,所以外层循环使循环变量m的值为101~199,每循环1次循环变量增长2。 for (m=101;m<=199;m+=2) 程序如下: #include<stdio.h> #include<math.h>//调用sqrt()函数需要包含math.h int main() { int m,k,i,n=0,flag; //设置标志变量flag,标记m是否被某数整除 for (m=101;m<=199;m=m+2) { flag=0; //flag初始值为0 k=sqrt(m); for (i=2;i<=k;i++) //循环变量i的值从2增长到m { if (m%i==0) //如果m被i整除,则m不是素数 { flag=1; //标志变量flag设为1并停止测试,跳出内层循环 break; } } if (flag==0) //如果标志变量为0,说明m是素数 { printf("%4d",m); //输出素数 n++; //计数器加1 if (n%7==0) printf("\n"); //输出7个素数就换行 } } //外循环结束 return 0; } 运行结果为 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 使用循环的嵌套结构要注意的问题: (1) 外层循环应完全包含内层循环,不能发生交叉。 例如,下面这种形式是不允许的: do {… for (…) {…while (…); } } (2) 循环嵌套中循环变量尽量不要同名,以免造成混乱。 例如,在下面的程序中,内、外层循环使用了相同的循环变量,因此改变了原有的循环次数。 for (i…) {… for (i…) {…} } (3) 注意,使用缩进格式来明确嵌套循环的层次关系,以增加程序的可读性。 5.6break语句和continue语句 前面学习了while语句、do while语句和for语句实现的循环结构,它们的共同特点是当循环条件满足时执行循环,只有在循环条件不满足的情况下才结束循环。然而有时需要根据一定的条件提前跳出循环,或者在满足某种条件时提前结束本次循环,这就要用到break语句和continue语句。 5.6.1break语句 break语句是限定转向语句,它能够使流程跳出所在的结构,转向执行该结构后面的语句。前面学习过可用break语句使流程跳出switch语句; break语句还可以使流程跳出它所在的循环结构,提前结束本层循环转去执行该循环结构后面的语句。一般形式为 图512break语句功能流程图 break; 执行过程: 程序执行时先计算表达式1的值,如果表达式1的值非0(逻辑真),则执行循环体。在循环体内,如果表达式b的值非0(逻辑真),满足了提前跳出条件,则执行break跳出本层循环,程序的流程转去执行循环结构后面的语句。如果表达式b的值为0(逻辑假),则继续执行循环体。执行流程如图512所示。从流程图可以看出,循环跳出的条件有两个: 要么表达式1不成立时正常结束循环,要么表达式b成立时提前结束循环。 说明: (1) break语句只能用于switch语句和循环语句中。 (2) break通常与if语句一起使用。当满足if的判断条件时提前终止循环,跳出相应的循环结构。 (3) 当break处于嵌套结构中时,它将使break语句所处的该层及内层结构循环中止,即break语句只能跳出它所在的循环,而对外层的结构没有任何影响。 【例58】读程序,分析程序运行结果。 程序如下: #include <stdio.h> int main() { inti, n; for (i=1;i<=5;i++) //循环应执行5次 { printf("Please enter n: "); scanf("%d", &n); if (n < 0)break; //如果输入的n值小于0,则提前跳出循环 printf("n = %d\n", n); } printf("Program is over!\n"); return 0; } 运行结果为 Please enter n: 15↙ n = 15 Please enter n: 20↙ n = 20 Please enter n: -3↙ Program is over! 上面程序中循环变量i的值从1~5每循环1次递增1,正常情况下应该执行5次循环。如果读入的n值小于0,则执行break语句提前跳出循环,转去执行循环后面的语句。只有当读入的n值都大于或等于0时,才能保证循环执行5次后正常结束。 5.6.2continue语句 continue语句也是限定转向语句,它的功能是提前结束本次循环,即跳过循环体中continue语句后面尚未执行的语句,重新开始下一次循环。一般形式为 continue; 说明: (1) continue语句只能用在while、do while和for这3种循环语句中。通常和if配合使用,当条件满足时提前结束本次循环并开始下一次循环,但是并没有终止整个循环的执行。 (2) continue语句在3种循环语句的执行流程略有不同。 在while和dowhile语句中,当循环条件满足时,则执行循环体中的语句1,然后判断表达式b是否满足,如果满足则越过continue后面的语句2,流程转去判断表达式1,从而开始下一次新的循环。如果表达式b的条件不满足,则继续执行语句2。如图513所示。 在for语句中,当表达式b的条件满足时,程序流程会跳过循环体中还未执行的语句2,转去执行表达式3,改变循环变量的值,然后再去执行表达式2进行循环条件的判断,根据判断结果决定for循环是否继续执行。如图514所示。 图513continue语句功能流程图 在while、do while语句中 图514continue语句功能流程图 在for语句中 【例59】求输入的10个整数中正数的个数及其和。 程序如下: #include <stdio.h> int main() { int i,x,s=0,n=0; for (i=1;i<=10;i++) { printf("请输入第%d个数: ",i); scanf("%d",&x); if (x<0) {n++;continue;} s=s+x; } printf("10个数中正数有%d个,其和: %d\n",10-n,s); return 0; } 运行结果为 请输入第1个数: 25↙ 请输入第2个数: 95↙ 请输入第3个数: -87↙ 请输入第4个数: 95↙ 请输入第5个数: 25↙ 请输入第6个数: -47↙ 请输入第7个数: -5↙ 请输入第8个数: 24↙ 请输入第9个数: 3↙ 请输入第10个数: 12↙ 10个数中正数有7个,其和: 279 从上面程序的执行过程可以看出,执行continue语句后不会改变程序原来设定的循环次数,只是会跳过continue后面的语句,继续下一次循环。 5.6.3break语句和continue语句的比较 break语句和continue语句都是流程转移语句,都可以应用到循环语句中,而且都要与if联合使用才有意义,使用时应注意它们的区别,如图515所示。 图515break语句和continue语句功能比较 break语句可以用在switch语句和循环语句中,其作用是跳出switch语句或跳出本层循环,转去执行switch语句或循环后面的语句。break一旦执行,就会改变原来程序的循环次数。 continue语句只能用于循环语句中,其作用是结束本层本次循环,不再执行循环体中continue 语句之后的语句,转入执行下一次循环。即使执行了continue语句,也不会改变原定的循环次数。 5.7循环结构程序设计举例 【例510】 输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。 解题思路: (1) 将回车符作为输入结束标志。 (2) 每输入一个字符,就判断字符的类型: 英文(大写字母'A'~'Z',小写字母'a'~'z')、空格(' ')、数字('0'~'9'),否则是其他字符。为每一类字符定义一个计数器,确定类型后就给相应的计数器加1。 程序如下: #include<stdio.h> int main() { char c; int letter=0,space=0,digit=0,other=0; //定义各类型字符的计数器,并初始化为0 while ((c=getchar())!='\n') //当输入的字符不是回车则执行循环 { if (c>='a'&&c<='z'||c>='A'&&c<='Z') //如果是字母,计数器letter+1 letter++; else if (c==' ') //如果是空格,计数器space+1 space++; else if (c>='0'&&c<='9' ) //如果是数字,计数器digit+1 digit++; else //如果是其他字符,计数器other+1 other++; } printf("letter=%d,space=%d,digit=%d,other=%d\n",letter,space,digit,other); return 0; } 运行结果为 7a8b90 &*d2 #!abc↙ letter=6,space=2,digit=5,other=4 【例511】 输入一个正整数m,输出它是几位数,并将其逆序输出。 程序如下: #include<stdio.h> int main() { int m,n=0; scanf("%d",&m); //输入一个整数m do { printf("%d ",m%10); //输出个位上的数字 m=m/10; //将m更新为去掉个位数的新m n++; //位数计数器+1 }while (m!=0); //直到新m=0时结束循环 printf("这个数是%d位数\n",n); return 0; } 运行结果为 123456↙ 6 5 4 3 2 1 这个数是6位数 图516菱形图形 【例512】输出如图516所示的菱形。 解题思路: (1) 图形的输出一般用双重循环来实现。外层循环控制行数,内层循环控制每行中的列数及每列的内容,然后在内外层循环之间加上printf("\n") 实现换行。 (2) 本题中的菱形可以分成两部分输出(上三角和下三角)。 上三角中随着行数i的增加每行多2个“*”, 而且“*”的个数k和行数i满足关系k=2*i-1。同时注意控制每行第一个“*”的输出位置,可以通过输出逐行递减的空格实现。 对于下三角,设置行数i由3递减到1,随着行数i的递减每行少2个“*”, 而且“*”的个数k和行数i满足关系k=2*i-1。同时注意控制每行第一个“*”的输出位置,可以通过输出逐行递增的空格实现。 程序如下: #include <stdio.h> int main() { int i,j,k; for (i=1;i<=4;i++) //输出上三角 { for (j=1;j<=(11-i);j++) //输出逐行递减的空格 printf(" "); for (k=1;k<=2*i-1;k++) //输出2*i-1个"*" printf("*"); printf("\n"); //换行 } for (i=3;i>=1;i--) //输出下三角,控制输出3行 { for (j=1;j<=(11-i);j++) //输出逐行递增的空格 printf(" "); for (k=1;k<=2*i-1;k++) //输出2*i-1个"*" printf("*"); printf("\n"); //换行 } return 0; } 上面程序中将菱形分为上三角和下三角两部分输出,而两部分输出的方法类似,只是外层循环变量的值不同。可用一个循环结构输出菱形,输出部分的程序段可优化为: for (i=-3;i<=3;i++) { for (j=1;j<=2+abs(i);j++) printf(" "); for (k=1;k<=6-(abs(i)*2-1);k++) printf("*"); printf("\n"); } 【例513】设有红、黄、绿3种颜色的球,其中红球3个、黄球3个、绿球6个,现将这12个球混放在一个盒子里,从中任意摸出8个球,编程计算摸出球的各种颜色搭配。 解题思路: 利用穷举法列出所有可能的组合,然后根据条件筛选出符合条件的组合。其中,红球可能值为: 0,1,2,3,黄球可能值为: 0,1,2,3,绿球可能值为: 2,3,4,5,6。 程序如下: #include<stdio.h> int main() { int x,y,z,n=0; //定义x、y、z分别表示红、黄、绿球个数 printf("red\tyellow\tgreen\n"); for (x=0;x<=3;x++) //红球可能值为0~3 for (y=0;y<=3;y++) //黄球可能值为0~3 for (z=2;z<=6;z++) //绿球可能值为2~6 if (x+y+z==8) //如果红黄绿三色球之和为8 { printf("%d\t%d\t%d\n",x,y,z); n++; //统计可能的组合方案个数 } printf("共有%d种\n",n); return 0; } 运行结果为 red yellowgreen 0 2 6 0 3 5 1 1 6 1 2 5 1 3 4 2 0 6 2 1 5 2 2 4 2 3 3 3 0 5 3 1 4 3 2 3 3 3 2 共有13种 【例514】输出1~1000的同构数。同构数是指一个数n恰好出现它的平方数的右端,如25和625是同构数。 解题思路: 依次取出1~1000的整数并求其平方,然后从个位数开始,取出平方数和该数对应的每一位上的数字进行比较,如果每一位上的数都相同,则是同构数,否则不是同构数。 程序如下: #include<stdio.h> int main() { int m,n,j; long k; for (m=1;m<1000;m++) { k=(long)m*m; n=m; while (n>0&&k%10==n%10)//将k和n的相应位置上的数进行比较 { k=k/10; n=n/10; } if (n==0) printf("%4d%10d\n",m,m*m); } return 0; } 运行结果为 11 525 636 25625 765776 376141376 625390625 【例515】求解定积分∫20x2+1)dx的近似值。 解题思路: 可以用矩形法求∫bafxdx的近似值,其算法为: (1) 将积分区间a,b分成长度相等的n个小区间,区间端点分别为x0,x1,x2,…,xn,其中x0=a,xn=b。其中,n值取得越大,其近似程度越好。 图517矩形法求定积分示意图 (2) 每一个小曲边梯形的面积用对应的矩形面积来代替,如图517所示。 (3) n个小矩形的面积之和,就是定积分的近似值。 用矩形法求定积分的公式为 ∫bafxdx=∑n-1i=0f(xi)(b-a)/n 每个小矩形的宽度为 h=b-an 端点为 xi=a+ih 对∫20(x2+1)dx而言,有 a=0,b=2,h=(2-0)/n,f(x)=x2+1 程序如下: #include<stdio.h> int main() { int n,i; double a,b,x,h,f,s=0; printf("请输入划分的区间数n: "); scanf("%d",&n); //输入划分的区间个数n,n值越大近似程度越好 printf("请输入积分的下限和上限: "); scanf("%lf%lf",&a,&b); //输入积分的下、上限 h=(b-a)/n; //求矩形的宽度 for (i=0;i<n;i++) { x=a+i*h; f=x*x+1; //矩形高度 s=s+f*h; } printf("积分值为: %.2f\n",s); return 0; } 运行结果为 请输入划分的区间数n: 500↙ 请输入积分的下限和上限: 0 2↙ 积分值为: 4.66 小结 在程序中不断被重复执行的结构称为循环结构。C语言提供了4种实现循环的语句: while语句、do while语句、for语句,以及非限定性跳转语句goto语句。其中goto语句必须与if结合使用才能实现循环结构,但结构化的程序设计一般不提倡使用goto语句,因此本书不作介绍。 (1) while语句、do while语句和for语句都可以用来处理同一问题,一般情况下它们可以互相代替。 (2) while语句和do while语句用在循环次数不确定,但循环条件明确的循环中; for语句使用最为灵活且结构紧凑,不仅可以用于循环次数已经确定的循环结构,还可以用于循环次数不确定只给出循环结束条件的循环结构中,它完全可以代替while语句。 (3) while语句、do while语句和for语句可以相互嵌套组成多重循环,嵌套的循环之间可以并列但不能交叉。 (4) while循环、do while循环和for循环,可以用break语句提前跳出循环,用continue语句结束本次循环。而对用goto语句和if语句构成的循环,不能用break语句和continue语句进行控制。 本章常见错误分析 常见错误实例常见错误解析 while (i<=10) { s=s+i; i++; } 在循环开始前,没有给循环变量i和累加器s赋初值。可改为: int i=0,s=0; while(i<=10) {s=s+i; i++;} int i=0,s=0; while (i<=10) s+=i; i++; while语句的循环体两端没有加{ }使之成为复合语句。应改为: int i=0,s=0; while(i<=10) {s+=i; i++;} for (i=1;i<=10;i++); { … } 或 while (i<=10); {… i++; } for语句、while语句后面加了分号,使循环体成为空循环。可改为: for(i=1;i<=10;i++) { … } 或 while(i<=10) {… i++; } int i=0,s=0; while (i<=10) { s+=i; } 循环体中少了改变循环变量的表达式,使循环无法趋向结束,形成死循环。应改为: int i=0,s=0; while(i<=100) {s+=i;i++; } do { … }while (i<=10) while后面缺少了分号,会产生编译错误,应改为: do { … }while(i<=10); for (i=1,i<10,i++){ … } for循环中表达式1,表达式2和表达式3之间应该用分号分隔,不是逗号,应改为: for(i=1;i<=10;i++) { … } while (a=3) { … } while后面的表达式是赋值表达式,使循环成为一个条件恒为真的死循环,应改为: while(a==3) { … } 习题5 1. 基础篇 (1) C语言中常用的实现循环结构的控制语句有()语句、()语句和()语句。 (2) do while语句和while语句的区别主要是()。 (3) break语句和continue语句的区别是()。 (4) 阅读程序,分析程序运行结果为()。 int i, k; for (i=0,k=-1;k=1;i++,k++) printf("! ! ! "); (5) 阅读程序,分析下述循环的循环次数为()。 int k=2; while (k==0) printf("%d",k); k--; printf("\n"); (6) 阅读下列程序,分析程序的运行结果为()。 #include<stdio.h> int main() { int y=10; while (y--); printf("y=%d\n", y); return 0; ) 2. 进阶篇 (1) 阅读下列程序,分析程序的运行结果为()。 #include<stdio.h> int main() { int x=23; do { printf("%d", x--); }while (!x); return 0; } (2) 阅读下列程序,分析程序的运行结果为()。 #include<stdio.h> int main() { int x=3,y; do { y=x-1; if (!y){ printf("*"); break;} printf("#"); } while(x>=1&&x<=2); return 0; } (3) 阅读程序,若下述程序运行时输入的数据是'A',则输出结果是()。 #include<stdio.h> int main() { int c; while ((c=getchar())!= '\n') { switch (c-'2') {case 0 : case 1 : putchar(c+4); case 2 : putchar(c+4); break; case 3 : putchar(c+3); default: putchar(c+2); break; } } printf("\n"); return 0; } (4) 阅读程序,下述程序的输出结果是()。 #include<stdio.h> int main() { int i,j,x=0; for (i=0;i<2;i++) { x++; for (j=0;j<=3;j++) { if (j%2) continue; x++; } x++; } printf("x=%d\n", x); return 0; } (5) 计算一个数列的前n项之和。该数列的前两项是由键盘输入的正整数,以后各项按下列规律产生: 先计算前两项之和,若和小于200,则该和作为下一项,否则用该和除以前两项中较小的一项,将余数作为下一项。为实现上述功能,请在[填空1]、[填空2]、[填空3]处填入正确内容。 #include<stdio.h> int main() { int n, k1, k2, k3, m, ms, j; scanf("%d%d%d",&n,&k1,&k2); m=k1+k2; [填空1] for (j=3;j<=n;j++) { if ( [填空2] ) if (k1<k2) k3=m%k1; else k3=m%k2; else [填空3]; ms=ms+k3; k1=k2; k2=k3; m=k1+k2; } printf("%d\n", ms); return 0; } (6) 下面程序的功能是: 输入一组数,输出其中最大值和最小值,输入0时结束。为实现上述功能,请在[填空1]、[填空2]、[填空3]处填入正确内容。 int main() { float x,max,min; scanf("%f",&x); max=x;min=x; while ([填空1]) { if (x>max) max=x; if ( [填空2] ) min=x; [填空3] } printf("max=%f\nmin=%f\n", max, min); return 0; } 3. 提高篇 (1) 下面程序的功能是: 输出5!+6!+7!+8!+9!+10!的结果。程序中有3处错误,请找出错误并改正。注意: 不得增行或删行,也不得更改程序的结构。 #include<stdio.h> int main() { long s,t; int i,j; for (i=5;i<=10;i++) { t=i; for (j=1;j<=i;j++) t=t*j; s=t; } printf("%ld\n",s); return 0; } (2) 以下程序的功能是: 求出1*1+2*2+…+n*n<=1000中满足条件的最大的n。程序中有3处错误,请找出错误并改正。注意: 不得增行或删行,也不得更改程序的结构。 #include <stdio.h> int main() { int n,s; s==n=0; while (s>1000) { ++n; s+=n*n; } printf("n=%d\n",&n-1); } (3) 编程计算a+aa+aaa+…+aa…a(n个a)的值,n和a从键盘输入。 (4) 输出所有的“水仙花数”。所谓“水仙花数”是指一个3位数,其各位数字立方和等于该数本身。例如,153是一个水仙花数,153=13+53+33 。 (5) 输出1~1000中能同时被3和5整除的前10个数。 (6) 求1+11×2+12×3+13×4+…+1n×(n+1) ,直到最后一项的值小于10-2 ,如果累加到第 20 项(即 n=19)时,最后一项的值还不小于 10-2,则不再计算,要求输出n的值、最后一项的值、多项式之和。 (7) 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如,6的因子1、2、3,而6=1+2+3,因此6是“完数”。编程序找出1000之内的所有完数。 (8) 设N是一个4位数,它的9倍恰好是其反序数,求N。反序数就是将整数的数字倒过来形成的整数。例如: 1234的反序数就是4321。 (9) 猴子吃桃问题: 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半多一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少个桃子。 (10) 编程设计一个简单的猜数游戏。程序运行时自动产生一个随机整数,用户输入猜的数字,如果猜对了,则显示“Right”,如果猜错了,则显示“Wrong!”,并告诉用户所猜的数是大还是小。 (11) 百钱百鸡问题。中国古代数学家张丘建在他的《算经》中提出了著名的“百钱买百鸡问题”: 鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何? (12) 编写程序: 输出如下图形。 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9