第5章 程序的循环结 构 思考题 1. 计算1+2+3+…+的值 。 对于这个问题,读者可能首先想到的是如下的数学方法 : 1+2+3+…+100=(1+100) + (2+99)+… + (50+51)=101×50=5050 但与之类似地,对于以下倒数的求和,却不易直接用该数学方法计算: 11 11+ 2+ 3+…+100 其实,无论是计算1+2+3+…+100,还是计算1+1/2+1/3+…+1/100,二者都有一 个共性:重复进行了若干次有规律的加法运算的操作。 又比如,计算 n 的阶乘n!, 即计算1×2×3×…(n-1)×n,它只不过是把上述重复进 行的若干次加法运算改成重复进行若干次乘法运算,求解过程的差异仅此而已。 2. 输入一个正整数n,输出它的所有因子。 求一个正整数 n 的所有因子,也就是在[n]。若能够 1,区间上逐一用每一个整数去除 n 整除,则该数就是 n 的因子之一。它是一个重复进行整除测试的过程。 3. 利用下面的极限公式计算圆周率π的近似值: π 111 4 =1-3+ 5 -7+ … 计算机的运算速度快,最善于进行重复性的工作。在利用计算机解题时,往往可以把复 杂的不容易理解的求解过程转换为易于理解的操作的多次重复,这就是循环。就像上面的 计算n!和计算1+1/2+1/3+…+1/100,虽然我们不能像1+2+3…+100 那样找出一个 简单的数学公式就能得出结果,但我们可以通过构造循环结构的程序,交给计算机快速实现 重复操作而得出结果。上面的“思考题2”和“思考题3”,同样也是可以通过循环结构的计算 机程序来求解问题。 5.程序的循环控制 1 在程序设计中,如果需要重复执行某些操作,就要用到循环结构。循环结构有两种形 式:当型循环和直到型循环,其对应的程序执行流程如图5-1所示。它们都是按照给定的 条件重复地执行某一个语句或语句组(复合语句), 区别在于:当型循环是先判断循环条件 是否满足,才确定是否开始循环操作;直到型循环则是先执行第一次循环操作,再按照循环 条件确定是否执行后续的循环操作。 图5-1 两种循环结构的执行流程图 循环结构是程序的3种基本控制结构中最复杂的一种 。 对于循环结构的设计,首先要明确两个问题 : (1)哪些操作需要重复执行? (2)这些操作在什么情况下重复执行? 这两个问题的解决分别对应循环体的设计和循环过程的设计。循环过程由3个要素组 成:循环过程控制量的初值、循环条件、使循环趋于结束的循环过程控制量的“增值”。只有明 确了这些问题,才能完整地设计出一个程序的循环结构,与此对应的程序执行流程如图5-2 所示。 图5-2中标示的“循环变量”以及后续的同样表述,指的就是“循环过程控制量”。“循环 初始化”则包括循环变量的初始化和循环体相关量的初始化。 例如,计算1+2+3+…+99+100 的值,可以这样设计求解过程: 首先设置一个累加结果变量sum,其初值为0,利用sum=sum+i 这个赋值操作,让计 算机重复进行加法运算和赋值操作。第一次加法运算时i的初值为1,之后的每次加法运算 的i值都比前一个i值多1,即:每次加法运算和赋值操作后使i=i+1,从而使i值依次取 1,2,99, 3,…,100 。通过重复100 次这样的加法运算和赋值操作,即可得到最后的结果。 显然,在这个求解过程中,循环(重复操作)的主体就是sum=sum+i 和i=i+1 。那么 如何控制循环的进程,确保正确地进行了100 次的重复操作呢? 也就是说,如何设计循环变 量的初值、循环条件和循环变量的“增值”? 可以增加一个循环变量j,每一次循环后使j值增1, 增 j的初值为1; 即循环变量j的“ 第 5 章程序的循环结构 65 图5-2 完整的循环结构流程图 值”方式是j=j+1;当j值增加到100 以后结束循环,即循环条件是j<=100 。 根据以上分析,不难画出图5-3(a)所示的程序流程图,它是一个当型循环结构。 图5-3 计算1+2+3+…+99+100 的循环流程图 对图5-3(a)所示的流程图进一步分析,可以发现:其中所设置的循环变量j与循环体中 的i,在循环过程中其实是完全同步一致的,因而可以将i和j合二为一,简化程序代码。简 化后的程序流程如图5-3(b)所示。 依照上述“计算1+2+3+…+99+100”的算法逻辑,我们可以轻松地画出计算1+1/2+ 1/3+…+1/100 和计算n! 的程序流程图,分别如图5-4和图5-5所示。图5-5中的f是用 于存放n! 计算结果的变量。 C语言提供了while语句、do-while语句和for语句这3种循环语句来构造程序的循环 结构。 66 C 语言程序设计 第5 章 程序的循环结构 67 图5-4 计算1+1/2+1/3+…+1/99+1/100的流程图图5-5 计算n! 的流程图 5.2 while语句 while语句的基本形式为: while(表达式) 循环体语句; 或 while(表达式) { 多条循环体语句; } while语句的常用形式: 循环初始化; while(循环条件) { 循环体; 循环变量改变; } while语句的流程如图5-6所示。 说明: (1)while语句构造的循环是一个当型循环,一般用于根据某个条件控制循环的情形。 (2)在while语句的基本形式中,while后面的表达式是作为循环条件用的。当表达式 的值为真(非0)时,就反复执行循环体语句;一旦表达式的值为假(0值),即结束循环,它的 执行流程如图5-6(a)所示。为了使循环能够结束,循环体语句中一般应含有使循环趋于结 束的循环变量“增值”的语句。 68 C 语言程序设计 图5-6 while语句的流程图 (3)如前所述,一个完整的循环结构包括两大部分:循环主体和循环过程控制。同样 地,使用while语句构造一个完整的循环结构,也需要将这两个方面都构造完整。这正是 图5-6(b)所示的while语句常用形式所体现出来的循环结构的算法逻辑和程序流程。 (4)为了明确标识循环体的范围,注意正确使用锯齿形书写格式,使循环体语句向右缩 进对齐。 (5)当循环体中的语句不止一条时,必须用{}将循环体语句组合成一条复合语句。即 使循环体中的语句只有一条,也可以用{}将其括起来,以明确标识其循环体的性质,提高程 序的可读性。 (6)还需要注意的是:正常情况下,while(表达式)后面紧跟的是一个非空操作的循环 体语句,而不能紧跟一个分号“;”。如果while(表达式)后面紧跟的是一个分号,则表示循环 体为空,同时该分号还表示while语句已结束,其后的语句已不是循环体语句,从而造成了 空循环操作,并极有可能造成死循环。一旦程序运行出现死循环,一般可按Ctrl+Break组 合键以终止程序的运行。 例5.1 使用while语句建立循环结构的程序,计算1+2+3+…+99+100的值。 在本章开头,已经就本问题的求解过程做了详细分析和描述,它可以用迭代与递推的循 环结构来求解,迭代关系式是sum=sum+i,i值依次取1,2,3,…,99,100。按照while语 句的语法格式,对比图5-6(b)和图5-3(b)所示的流程图,不难写出求解本问题的程序代码。 01 #include<stdio.h> 02 main() 03 { int i=1,sum=0; 04 while(i<=100) 05 { sum=sum+i; 06 i++; 07 } 08 printf("sum=%d\n",sum); 09 } 程序运行结果: 第5 章 程序的循环结构 69 sum=5050 说明: (1)当循环体中的语句不止一条时,必须用{}将循环体语句组合成一条复合语句。如 果将本例中while语句循环体的{}去掉: while(i<=100) { sum=sum+i; i++; } .变 成 while(i<=100) sum=sum+i; i++; 则while语句将变成死循环。因为此时的循环体语句只有“sum=sum+i;”,而“i++;”已 不属于循环体的语句,从而导致循环变量i不会出现任何变化,i总是保持初值1不变,循环 条件永远满足,这就造成了死循环(此时可按Ctrl+Break组合键以中断程序)。 (2)初学者还会常犯另一个错误:在while(表达式)后面紧跟一个分号。例如: while(i<=100) { sum=sum+i; i++; } .变 成 while(i<=100); { sum=sum+i; i++; } 此时,while(表达式)后面紧跟的分号“;”代表的就是循环体,只不过“它什么也没做”。 之后的{}部分已不属于循环体。这样也导致了循环变量i不会出现任何变化,循环条件永 远满足而造成死循环。 (3)如果将本题循环迭代的i值取值方向反过来,即i值依次取100、99、…、3、2、1,那 么,作为循环变量的i,其“增值”则是一个逐渐减值方向的“减值”,即i=i-1,同时循环条件 要调整为while(i>0)。循环部分的代码如下: 01 int i=100,sum=0; 02 while(i>0) 03 { sum=sum+i; 04 i--; 05 } 例5.2 使用while语句建立循环结构的程序,计算以下公式的值。 1+12 +13 + … + 1 99+ 1 100 本问题的求解方法与例5.1相同,它可以用迭代与递推的循环结构来求解,迭代关系式 是sum=sum+1.0/i,i值依次取1、2、3、…、99、100,程序流程如图5-4所示。 但需要注意的是: (1)存储累加和的变量sum,必须定义成浮点数类型(double型或float型)。 (2)每次循环迭代进来的是一个分数项1.0/i,要特别注意分数项的计算特点:分子和 分母要以浮点数方式进行运算。因为分子和分母不能是两个整数相除,否则依据C语言整 数除法的语法规则,分子和分母两个整数相除的结果只能保留整数部分。 要使分子和分母以浮点数方式进行运算,有两种解决办法:一是优先保障分子和分母 70 C 语言程序设计 整型数据的性质不变,但在进行分数运算时,将分子和分母的任一方临时转换为浮点型数据 来参与分数的运算;二是将分子和分母的任一方直接定义为浮点型变量,但这改变了分子和 分母整型数据的性质。本例按第一种办法进行处理。 01 #include<stdio.h> 02 main() 03 { double sum=0; 04 int i=1; 05 while(i<=100) 06 { sum=sum+1.0/i; //或sum=sum+1/(double)i; 07 i++; 08 } 09 printf("sum=%f\n",sum); 10 } 程序运行结果: sum=5.187378 例5.3 输入一个正整数n,输出它的所有因子。 分析:求一个正整数n的所有因子可以采用穷举法,对1~n的全部整数进行测试判 断,凡是能够整除n的均为n的因子。 程序: 01 #include<stdio.h> 02 main() 03 { int n,i=1; 04 printf("请输入一个正整数: "); 05 scanf("%d",&n); 06 printf("它的因子是: "); 07 while(i<=n) 08 { if(n%i==0) printf("%d, ",i); 09 i++; 10 } 11 } 例如,输入18得到的程序运行结果为: 请输入一个正整数: 18 它的因子是: 1, 2, 3, 6, 9, 18, 5.3 do-while语句 do-while语句的基本形式: do 循环体语句; while(表达式); 第5 章 程序的循环结构 71 或 do { 多条循环体语句; }while(表达式); do-while语句的常用形式: 循环初始化; do { 循环体; 修改循环变量; }while(循环条件); do-while循环语句的流程如图5-7所示。 图5-7 do-while语句的流程图 说明: (1)do-while语句构造的循环是一个直到型循环,一般用于根据某个条件控制循环。 (2)do-while语句与while语句的区别:do-while语句先执行一次循环体语句,然后再 对循环条件进行判断,循环体语句至少被执行一次;while语句则先判断后循环,有可能一 次也不执行循环体语句。当while后面表达式的值第一次循环就为真(非0)时,while语句 与do-while语句完全等价。 (3)还需要注意的是:在do-while语句中,while(表达式)后面有分号“;”,它是do-while 语句结束的标志。如果漏掉了该分号,将造成语法错误,程序不能编译通过。而在while语 句中,正常情况下while(表达式)后面不能紧跟一个分号“;”,否则将造成空循环,并极有可 能导致死循环。前面已对此做了详细说明,在此不再赘述。 例5.4 将例5.1中计算1+2+3+…+99+100的程序,改用do-while语句实现。 对比图5-6(b)所示的while语句流程图和图5-7(b)所示的do-while语句流程图,再参 72 C 语言程序设计 照例5.1的处理方法,按照do-while语句的语法格式,不难写出如下的程序代码。 01 #include<stdio.h> 02 main() 03 { int i=1,sum=0; 04 do 05 { sum=sum+i; 06 i++; 07 }while(i<=100); 08 printf("sum=%d\n",sum); 09 } 5.4 for语句 for语句的基本形式: for(表达式1; 表达式2; 表达式3) 循环体语句; 或 for(表达式1; 表达式2; 表达式3) { 多条循环体语句; } 图5-8 for语句的流程图 for语句的流程图如图5-8所示。 说明: (1)与while语句一样,for语句构造的循环也是一个当型 循环。如 图5-8所示,for语句的执行流程是:首先计算表达式1的 值(只计算一次),再计算表达式2(循环条件)的值,并根据表达式 2的值判断是否执行内嵌的循环体语句;如果表达式2的值为真 (非0)时,则执行循环体语句,并紧接着计算表达式3的值,再回 头计算表达式2的值,并根据表达式2的值判断是否执行循环 体,……,如此循环往复。一旦表达式2的值为假(0值),即结束 循环。 (2)对比图5-8“for语句的流程图”和图5-2(a)“完整的循环结构流程图-当型循环结 构”,显而易见,两者可以完全对应。for语句括号中的3个表达式“表达式1、表达式2、表达 式3”,分别对应“循环初始化、循环条件、循环变量改变”。即与循环结构基本逻辑完全对应 的for语句的应用形式: for(循环初始化;循环条件;循环变量改变) { 循环体语句; } 第5 章 程序的循环结构 73 (3)注意在“for(表达式1;表达式2;表达式3)”中,分隔3个表达式的是两个分号“;” 而不是逗号。 (4)与while语句和do-while语句一样,当循环体中的语句不止一条时,必须用{}将循 环体语句组合成一条复合语句。同样还要注意正确使用锯齿形书写格式,使循环体语句向 右缩进对齐,以明确标识循环体的范围,提高程序的可读性。 (5)还需要注意的是:正常情况下,“for(表达式1;表达式2;表达式3)”后面紧跟的 是一个非空操作的循环体语句,而不能紧跟一个分号“;”,否则将造成空循环。这样的空循 环,虽然不会像while语句那样导致死循环,但循环的结果却是“什么都没做”。 (6)表达式2是空语句时,认为是非0,即真。如“for(i=0;;i++);”就是死循环。 例5.5 将例5.1中计算1+2+3+…+99+100的程序,改用for语句实现。 分析:for语句与while语句一样属于当型循环,它们求解问题的算法逻辑是一致的。 因而可以依照图5-3(b)所示的程序流程图和for语句的语法格式,写出下面的程序代码。 01 #include<stdio.h> 02 main() 03 { int i,sum; 04 for(sum=0,i=1;i<=100;i++) 05 { sum=sum+i; 06 } 07 printf("sum=%d\n",sum); 08 } 例5.6 按照格雷戈里公式:π 4=1-13 +15 -17 +…,求π的近似值,直到最后一项的 绝对值小于10-6。 分析:图5-4中已经给出了计算1+1/2+1/3+…+1/100的程序流程图,而本题所使 用的格雷戈里公式与其非常相似,同样可以把它看成一个不断累加求和的过程,所不同的 是:每一次累加进来的分数项,正负符号位要改变,分母取值的变化率不同,累加的次数是 未知的,但累加结束的时机是确定的,即循环条件仍然是明确的。 若累加和用sum 表示,每次被加的分数项用t表示,分数项的分母用n表示,分数项的 符号位(正负号)用sign表示。参照前面类似问题求解过程的分析和循环流程的设计,可以 发现求解本问题的循环结构,其循环体包括sum=sum+sign*t,以及t的取值(t=1/n)和 符号位sign的转换。sign的初值取1,则每一次循环,通过sign=-sign的运算,即可使符 号位正负转换。显然,这也是通过迭代算法构造的循环,迭代关系式有两个:sum=sum+ sign*t和sign=-sign。 那么循环的进程又该如何设计呢? 可以将分数项的分母n直接作为循环变量,n的初 值取1,每一次循环后使n=n+2,从而使n值依次取1、3、5、7……直到1/n小于0.25× 10-6,即循环条件是t≥10-6。 通过以上分析,不难写出下面的程序代码。 01 #include<stdio.h> 02 main() 03 { int sign=1,n=1; //sign 用于设置正负号,n 代表分母