在实际应用中,经常会遇到一些需要重复处理的问题,在C语言中,这类问题可以通过 循环语句解决。例如,在机器博弈中,对弈双方反复交替落子、棋盘的输出等,都需要用到循 环结构。C语言提供了3种循环语句,分别是while语句、do…while语句和for语句。 5.1 引例 例5.1 假设一辆公交车从始发站出发,中间经过19站到达终点站。编写程序,计算一 辆公交车从始发站到终点站总共的上车人数,每一站的上车人数可由随机函数产生。 图5-1 例5.1程序流程图 【分析】 (1)公交车从始发站到终点站总共有20站可能有乘客上车。 从实际出发,可以假设每一站的上车人数为0~10,即在程序中控 制随机数的产生范围是[0,10]。 (2)每一站进行的都是类似的操作,即产生随机上车乘客人 数,并将人数累加。相同或相似操作的反复执行,可以使用循环语 句实现。例5.1程序流程图如图5-1所示。 01 #include<stdio.h> 02 #include<stdlib.h> 03 #include<time.h> 04 int main(void) 05 { 06 int i=1,sum=0; //i 变量控制重复次数,初始乘客人数sum 为0 07 int num; //每一站上车的人数 08 srand((unsigned int)time(NULL)); //初始化随机数发生器 09 while(i<=20) 扫一扫 64 10 { 11 num =rand()%11; //产生一个0~ 10 的随机数作为每站上车人数 12 sum=sum+num; //计算累加和 13 i++; 14 } 15 printf("sum=%d\n",sum); 16 return 0; 17 } 程序运行结果如下: sum=99 【注意】 本例中设定公交车始发时车上没有乘客,因此sum 变量的初始值赋值为0。 对于累加运算,求和变量赋初值为0的情况较为多见。 5.2 3种循环语句 C语言中的循环语句分为如下两种类型。 (1)当型循环。该类型循环首先判断条件是否为真,为真时反复执行相应的语句,否则 结束循环。while语句和for语句构成的循环属于当型循环。 (2)直到型循环。该类型循环首先进入循环体,执行相应的语句,然后再判断条件是否 为真,为真时反复执行语句,否则结束循环。do…while语句构成的循环属于直到型循环。 循环结构中反复执行的语句称为循环体。循环体可以只有一条语句,也可以是复合 语句。 5.2.1 while 语句 while语句的一般形式如下: while(表达式)语句 图5-2 while语句的流程图 while语句的流程图如图5-2所示,其执行过程如下。 (1)判断表达式是否为真; (2)如果表达式为真,那么执行循环体语句,返回步骤(1), 否则转步骤(3); (3)结束循环,执行循环语句后面的语句。 5.2.2 do…while 语句 do…while语句的一般形式如下: do 语句 while(表达式); 65 图5-3 do…while语句 的流程图 do…while语句的流程图如图5-3所示,执行过程如下。 (1)执行循环体语句; (2)判断表达式是否为真,如果表达式为真,则返回步骤(1),否 则转步骤(3); (3)结束循环,执行循环语句后面的语句。 可见,while语句在执行时先判断条件,当条件为真时执行循环 体语句,属于当型循环;而do…while语句则先执行循环体语句,后 判断条件,属于直到型循环。 例5.2 天天向上的力量:分别用while语句和do…while语句求xn。 图5-4 例5.2方法1程序流程图 【分析】 (1)采用输入函数分别为变量x和n输入一个确定 的值; (2)可以定义一个变量s保存最终计算结果,初值为1。 再令一个变量i按照每次增1的规律从1变到n,控制循环 执行n 次,每次将一个x 的值与s相乘,最终求得xn。 例5.2方法1程序流程图如图5-4所示。 方法1:用while语句实现。 01 #include<stdio.h> 02 int main(void) 03 { 04 int i,n; 05 float x,s; 06 printf("Enter x,n:"); 07 scanf("%f,%d",&x,&n); 08 i=1; 09 s=1; //累乘的初始值为1 10 while(i<=n) 11 { 12 s=s*x; //累乘运算 13 i++; 14 } 15 printf("s=%.4f\n",s); 16 return 0; 17 } 程序运行结果如下: Enter x,n: 1.01,365 ↙ s=37.7833 方法2:用do…while语句实现。 01 #include<stdio.h> 02 int main(void) 03 { 04 int i,n; 05 float x,s; 06 printf("Enter x,n:"); 07 scanf("%f,%d",&x,&n); 08 i=1; 09 s=1; 10 do 11 { 12 s=s*x; 13 i++; 14 }while(i<=n); 15 printf("s=%.4f\n",s); 16 return 0; 17 } 程序运行结果如下: Enter x,n: 0.99,365 ↙ s=0.0255 .......................................................................... 短跑运动员苏炳添用“一厘米”的手势,提醒自己“进步一点点就好”。观察例5.2的运 行结果,也体现了积少成多的道理。如果每天收获一点点,365天以后,就有一个大的飞跃; 如果每天放弃一点点,365天以后,就会落后很多。学如逆水行舟,不进则退,希望你也能够 日积月累、天天向上! 66 【说明】 (1)计算累乘时要注意结果变量的初始值,忘记赋值或赋值为0都将导致程序错误。 (2)do…while语句结束的分号切勿省略,否则会导致语法错误。 (3)一般情况下,while和do…while语句能够互换使用,解决同一问题时循环体部分 是相同的,如例5.2。但如果while后面的表达式一开始就为假,两种循环的执行结果则不 同。例如,下面两段程序是不等价的。 1 x=6; 2 sum=0; 3 while(x<=5) 4 { 5 sum=sum+x; 6 x++; 7 } 8 printf("%d\n",sum); 1 x=6; 2 sum=0; 3 do 4 { 5 sum=sum+x; 6 x++; 7 } while(x<=5); 8 printf("%d\n",sum); ............................ 在上面两段程序中,while循环由于一开始表达式值为假,循环体没有被执行,输出 sum 的值为初始值0;do…while循环由于先执行循环体语句,后判断条件,因此,执行了一 次循环体语句后才结束循环,输出sum 的值为6。 5.2.3 for 语句 for语句的一般形式如下: for(表达式1; 表达式2;表达式3)语句 图5-5 for语句的流程图 for语句的流程图如图5-5所示,其执行过程如下。 (1)求解表达式1; (2)判断表达式2; (3)如果表达式2为真,那么执行循环体语句,转步骤(4),否则 转步骤(5); (4)求解表达式3,返回步骤(2); (5)结束循环,执行循环语句后面的语句。 可见,for语句与while语句类似,也是在执行时先判断条件,当 条件为真时才执行循环体语句,属于当型循环。 例5.3 用for语句计算xn。 01 #include<stdio.h> 02 int main(void) 03 { 04 int i,n; 05 float x,s; 06 printf("Enter x,n:"); 07 scanf("%f,%d",&x,&n); 08 s=1; 09 for(i=1;i<=n;i++) 扫一扫 67 10 s=s*x; 11 printf("s=%.4f\n",s); 12 return 0; 13 } 程序运行结果如下: Enter x,n:1.01,730↙ s=1427.5784 【说明】 (1)从例5.3可见,for语句后面可以包含多个表达式,使程序更为简洁。一般情况下, 上述3种循环语句可以互相替代。 (2)如果不是循环体为空,不要在for语句和while语句的后面随意添加空语句,即分 号“;”,否则会使原本的循环体语句失去应有的作用,甚至导致死循环。例如: i=1; while(i<=n); { s=s*x; i++; } for(i=1;i<=n;i++); s=s*x; .................... 上述两个程序段均在循环表达式的右侧添加了分号,因此,两个循环的循环体都变成了 空语句,即循环体什么都不做,程序出现了逻辑错误。当n的值大于或等于1时,while循环 语句将会变成死循环。 【思考】 若要参照例5.2或例5.3计算n!,该如何修改程序? 5.3 计数循环 在程序中,循环次数已知的循环称为计数控制的循环。例如,查找所有的水仙花数、求 阶乘等问题一般都采用计数循环求解。习惯上,用for语句实现计数控制的循环可使程序 更为简洁。 5.3.1 计数循环的基本应用 图5-6 例5.4程序流程图 例5.4 计算1~m 所有奇数的和。 【分析】 (1)可以定义一个循环变量i,使其从1变到m 控制累加项 的范围; (2)i的值每次递增2,可以计算出下一个累加项的值。 例5.4程序流程图如图5-6所示。 01 #include<stdio.h> 02 int main(void) 68 03 { 04 int i,m; 05 long sum; 06 printf("Enter m:"); 07 scanf("%d",&m); 08 for(i=1,sum=0;i<=m;i+=2) //表达式1 中应用了逗号表达式 09 sum=sum+i; 10 printf("sum=%ld\n",sum); //长整型变量输出用ld 格式符 11 return 0; 12 } 程序运行结果如下: Enter m:5↙ sum=9 【说明】 (1)for语句后面的表达式1和表达式3既可以是简单的表达式,也可以是逗号表达 式,即包含多个简单表达式,中间用逗号分隔。例5.4中,for语句的表达式1就是逗号表达 式。虽然逗号表达式的应用使程序更为简洁,但过多地使用逗号表达式会使程序的可读性 变差。因此,尽量不要把与循环控制无关的语句写到for语句的表达式中。 (2)for语句中的3个表达式均可根据需要省略不写,但间隔的分号不能省略。当for 语句的表达式2省略时,循环条件按其值为真进行认定。例如,例5.4中的for语句写成如 下形式也是合法的。 for(i=1,sum=0;i<=m;) { sum=sum+i; i+=2; } i=1; sum=0; for(;i<=m;i+=2) sum=sum+i; .................. 例5.5 完善例4.5,输出所有的水仙花数。 01 #include<stdio.h> 02 int main(void) 03 { 04 int i,x,y,z,c; 05 for(i=999;i>=100;i--) //在所有三位数中查找 06 { 07 x=(int)(i/100); 08 y=(int)(i%100/10); 09 z=i%10; 10 c=x*x*x+y*y*y+z*z*z; 11 if(c==i) printf("%-4d",i); //输出水仙花数,占4 列宽,右侧补空格 12 } 13 return 0; 14 } 程序运行结果如下: 407 371 370 153 69 5.3.2 蒙特卡洛方法求π的近似值 蒙特卡洛(MonteCarlo)方法,又称随机抽样或统计试验方法。当所要求解的问题是某 种事件出现的概率,或某随机变量的期望值时,可以通过某种“试验”的方法求解。 例5.6 编写程序,用蒙特卡洛方法求圆周率π的近似值。 【分析】 (1)用蒙特卡洛方法求解π近似值的思路:构造单位正方形及其内切单位圆,然后随 机向单位正方形内抛洒大量点数,判断每个点是否在圆内,将圆内与正方形内的离散点数比 模拟为面积比,计算π的值。四分之一单位圆与单位正方形随机抛点的情况如图5-7所示。 (2)圆的面积/正方形的面积=πr2/(2r)2=π/4,当抛洒的点数足够多时,将圆和正方形 的面积比用它们的点数比替代,此时,圆内点数/正方形内点数=π/4,则π=4*(圆内点数/ 正方形内点数)。例5.6程序流程图如图5-8所示。 图5-7 蒙特卡洛法求π近似值模拟图 图5-8 例5.6程序流程图 01 #include<stdio.h> 02 #include<math.h> 03 #include<time.h> 04 #include<stdlib.h> 05 int main(void) 06 { 07 long darts,i; 08 double x,y,dist,pi,hits; 09 srand((unsigned int)time(NULL)); //初始化随机数发生器 10 scanf("%ld",&darts); //输入抛洒的点数 11 hits=0.0; //落在圆内的点数 12 for(i=1;i<=darts;i++) 13 { 14 x=1.0*rand()/RAND_MAX; //产生[0,1]区间随机数字x 坐标 15 y=1.0*rand()/RAND_MAX; //产生[0,1]区间随机数字y 坐标 16 dist=sqrt(x*x +y*y); //计算该点到原点的距离 17 if(dist<=1.0) //如果距离小于或等于1,则表示在圆内 18 hits=hits +1; //圆内点的个数加1 扫一扫 70 19 } 20 pi=4 * (hits/darts); //计算π的值 21 printf("%f\n",pi); 22 return 0; 23 } 程序运行结果1如下: 30000↙ 3.145067 程序运行结果2如下: 500000↙ 3.140552 【思考】 上述程序中,π值的精确程度与什么直接相关? 【说明】 蒙特卡洛方法是机器博弈中的基础算法,应用于爱恩斯坦棋、德州扑克、五子 棋、不围棋等多项棋牌游戏。 5.3.3 井字棋随机落子 例5.7 编写程序,在空的井字棋棋盘上随机落一个棋子,并打印棋盘当前状态。 【分析】 (1)按行访问井字棋棋盘,将其看作线性排列的9个位置,并依次编号为1~9。 (2)在1~9产生随机数作为落子位置,输出棋盘时在空白位置输出“+”,在落子位置 输出“●”或“○”,并进行换行控制。 01 #include<stdio.h> 02 #include<stdlib.h> 03 #include<time.h> 04 int main(void) 05 { 06 int i,position; 07 srand((unsigned int)time(NULL)); 08 position=rand()%9+1; //产生落子位置 09 for(i=1;i<=9;i++) 10 { 11 if(position==i) 12 printf("●"); //落子 13 else 14 printf("+"); 15 if(i%3==0) printf("\n"); //控制换行 16 } 17 return 0; 18 } 程序运行结果如下: 71 【说明】 在实际的博弈程序中,井字棋棋盘一般需要采用数组(第6章介绍)等方式保 存起来,以便于落子的合法性判断以及着法生成等其他博弈算法的应用。 5.4 条件循环 循环次数未知时,一般用一个条件控制循环体是否执行,这种类型的循环称为条件控制 的循环。该类循环采用while语句或do…while语句实现更为方便。 5.4.1 条件循环的基本应用 图5-9 例5.8程序流程图 例5.8 验证“角古猜想”。它是指对于一个自然数,若该数 为偶数,则除以2;若该数为奇数,则乘以3再加1,将得到的数再 重复按该规则运算,最终可得到1。 【分析】 对于任意输入的自然数,采用if语句进行奇偶性的 判断,然后按照相应的运算规则反复进行运算。由于循环次数未 知,采用while语句或do…while语句比较方便。例5.8程序流 程图如图5-9所示。 01 #include<stdio.h> 02 int main(void) 03 { 04 int n,i; 05 printf("Please enter a number:"); 06 scanf("%d",&n); 07 while(n!=1) //当n 值未到达1 时,重复执行循环体语句 08 { 09 if(n%2==0) //n 为偶数时 10 n=n/2; 11 else //n 为奇数时 12 n=n*3+1; 13 printf("%d ",n); //输出验证过程 14 } 15 return 0; 16 } 程序运行结果如下: 图5-10 例5.9程序流程图 Please enter a number:20↙ 10 5 16 8 4 2 1 例5.9 继续完善例4.7,随机产生一个1~50的 整数由用户去猜,直到猜对为止。猜测过程中给出 相应提示信息,若猜测数与随机数相等,显示猜中; 若猜测数小于随机数,显示猜小了;若猜测数大于随 机数,显示猜大了。例5.9 程序流程图如图5-10 所示。 72 01 #include<stdio.h> 02 #include<stdlib.h> 03 #include<time.h> 04 int main(void) 05 { 06 int magic,guess; 07 srand((unsigned int)time(NULL)); 08 magic =rand() %50 +1; 09 do 10 { 11 printf("Please guess a magic number:"); 12 scanf("%d",&guess); //用户输入猜测的数 13 if(guess==magic) printf("猜中!\n"); 14 else if(guess>magic) printf("猜大了!\n"); 15 else printf("猜小了! \n"); 16 }while(guess!=magic); //循环直到猜中为止 17 return 0; 18 } 程序运行结果如下: 图5-11 例5.10程序流程图 Please guess a magic number:25 ↙ 猜小了! Please guess a magic number:35 ↙ 猜小了! Please guess a magic number:40 ↙ 猜大了! Please guess a magic number:38 ↙ 猜中! 例5.10 根据近似公式:π4 =1-13 +15 -17 + … + (-1)n-1 1 2n -1,求圆周率π的值。要求最后一项的绝对值 小于10-6时结束求解,例5.10程序流程图如图5-11所示。 01 #include<stdio.h> 02 #include<math.h> 03 int main(void) 04 { 05 double t=1.0,pi=0.0,n=1.0; 06 int sign=1; 07 while(fabs(t)>=1e-6) 08 { 09 pi=pi+t; 10 n=n+2; 11 sign=-sign; //控制正负号 12 t=sign/n; //计算累加项 13 } 14 pi=pi*4; 15 printf("pi=%10.8lf\n",pi); 16 return 0; 17 }