1
C/C ++程序设计
1.2.4.3 4.3 循环结构

4.3 循环结构

4.3.1 循环结构的描述

循环结构也称重复结构,利用循环可以重复执行一个语句或语句块,循环的实现可以使得计算机在一定条件下重复工作。在程序设计中可能会出现两种循环,一种是无休止的循环即死循环,一种是有条件的循环即可控的循环。在实际问题中应避免出现死循环,因为它是没有意义的。在构造循环结构时一般包含四个要素:(1)循环的初始状态:循环变量赋初值;(2)循环结束的条件:决定循环何时终止;(3)循环体:反复执行的一个语句或语句块;(4)状态的改变:修改循环变量的值使循环趋向于结束。特别指出,如果缺少第4个要素则会出现死循环的情况,在程序设计时尤其应注意。

循环结构从执行流程上可分为两种类型:前测试循环和后测试循环。前测试循环是首先进行条件判断,条件成立则反复执行某语句(块),直到条件不再成立循环结束;后测试循环是先执行某语句(块)一次,再进行条件判断,条件成立则反复执行该语句(块),直到条件不再成立时循环结束。它们的流程图分别如图4-21和图4-22所示。C语言中的三种循环语句分别是while语句、for语句和do-while语句,其中while和for语句属于前测试循环,而do-while属于后测试循环。

img108

图4-21 前测试循环

img109

图4-22后测试循环

4.3.2 while语句

while循环的语法规则为:

while(表达式)

{

语句(块)}

说明:执行while循环时首先判断括号中表达式的值,如果值为真则执行{}中的语句,直到表达式的值为假,循环结束。

注意:表达式的括号后面是不加分号的。

【例4.13】用while循环语句求1+3+5+…+99的和。

img110

运行结果如图4-23所示。

img111

图4-23

分析:

题目要求计算从1~99之间奇数的和,因此循环的初始状态定义为number=1,判断循环是否结束的条件是number<=99,循环体为第7、8行。特别注意,第8行是状态改变语句,如果省略则会构成死循环。

4.3.3 for语句

for循环的语法规则为:

for(表达式1;表达式2;表达式3)

{

语句(块)

}

说明:括号中是用两个分号隔开的三个表达式。其中表达式1表明的是循环的初始状态,表达式2表明的是条件判断,表达式3的作用是表明状态的改变。执行for循环时,首先计算表达式1的值即初始状态,注意只计算一次。然后计算表达式2的值,如果为真则执行{}中的语句,接着计算表达式3的值即状态改变,重复上述过程直至表达式2的值为0(假),循环结束。

注意:for后面的括号中必须有两个分号,即使省略了某个表达式分号也不能省略。括号的后面没有分号。

【例4.14】用for循环输出如下图形。

************

************

************

************

img112

运行结果如图4-24所示。

img113

图4-24

分析:

题目中要求输出四行星号,由于每行星号个数一样,故循环体只有一句即第7行代码。for中的第一个表达式i=1是初始状态,条件为i<=4(只有4行),每次输完一行后对i值加1如表达式3所描述。

4.3.4 do-w hi l e语句

do-while循环的语法规则为:

do{

语句(块)

}while(表达式);

说明:具体执行过程为,首先执行{}中的语句(块)一次,然后判断表达式的值,为真则重复上述过程,直至表达式的值为0循环结束。与while和for语句不同的是,do-while属于后测试循环,因此{}中的语句(块)最少执行一次。

注意:在表达式的后面有一个分号,不可遗漏!

【例4.15】用do-while循环连续输入成绩(以负数成绩作为结束标志),计算总分数。

img114

img115

运行结果如图4-25所示。

img116

图4-25

分析:

题目中要求计算各个学生的总成绩,循环的结束条件为score<0。由于do-while为后测试循环,因此输入一个分数score后就加到了sum变量中,并没有测试这个分数的有效性,因此第10行代码是必要的,如果输入的成绩为负数,应该减去最后一次输入的负数,这样计算出来的才是学生的总成绩。

4.3.5 break语句与conti nue语句

break语句和continue语句属于流程跳转语句,在循环语句的执行过程中如果遇到break语句或者continue语句,则可跳出循环,在使用时一般结合if语句来完成其功能。以while循环为例,语句形式如下:

while(表达式1)

{

语句部分…

if(表达式2)

img117

语句部分…

}

但二者作用又有不同,具体用法与区别如下。

(1)break语句

break语句在选择结构的switch语句中出现过,作用是跳出switch语句。同样的,把break语句放入while、for或者do-while语句中也可退出循环结构。break用在循环中的作用是终止本层循环。其流程图如图4-26所示。

img118

图4-26 break的作用

【例4.16】要求用户不断输入整数,当连续输入的整数的和超过100时,停止输入数据并输出这些整数的和。

img119

运行结果如图4-27所示。

img120

图4-27

分析:

程序中第5行的while(1)构造了一个条件永远为真的循环,如果循环体中没有特殊的语句,则构成一个死循环。因此第10行的条件判断,程序在不断求和的同时也在不断判断sum的值,一旦大于等于100则执行break语句,循环可以正常结束。可见break语句在本题中可以起到跳出整个while循环语句的作用。

(2)continue语句

continue语句的作用与break有所不同,如果在循环语句的循环体中出现continue语句,则跳过本次循环体中剩余的尚未执行的语句,经过修改循环变量的值后进行下一次循环的条件判断。概括起来就是continue语句可以结束本次循环。其流程图如图4-28所示。特别指出,continue语句并不是结束整个循环,而是仅仅结束本次循环。

img121

图4-28 continue的作用

【例4.17】利用循环读入6个学生的成绩,并计算总分,要求过滤掉无效的分数。

img122

img123

运行结果如图4-29所示。

img124

图4-29

分析:

题目中要求了成绩的个数,因此程序中定义了一个计数器变量count控制个数,成绩应该从0~100是有效的,因此凡是不符合该范围的执行到第9行时,程序会执行continue语句,从而结束本次循环即跳过后面的第11、12和13行的执行,重新回到for循环中的表达式2进行判断。

4.3.6 循环结构和选择结构的嵌套

循环结构可以包含选择结构,在程序设计中也经常需要这种技术。例如,现需要对大量数据进行筛选,挑选出符合要求的数据,这时候就可以用一个循环结构来遍历这些数据,在循环体内加入选择结构进行判断,这种选择结构就好比是一个“筛子”,筛出来的数据就是你所需要的数据。

【例4.18】输出200以内能被3整除但不能被5整除的整数。

img125

运行结果如图4-30所示。

img126

图4-30

分析:

题目要求找出200以内能被3整除但不能被5整除的整数,可用for循环遍历从1~200的整数,每一个整数都需要进行判断即执行第8行语句,这就是用if语句构成的一个“筛子”,符合这个条件的就是所需要的数据。变量count是一个计数器,主要是为第12、13行服务,运行结果中每行有6个整数,用第12、13行语句来控制换行。

4.3.7 循环结构的嵌套

循环的嵌套即在一个循环语句中又包含另外一个循环语句,3种循环语句既可以自身进行嵌套也可以互相嵌套,但是在嵌套时一个循环必须完整地包含另外一个循环。以for循环的嵌套为例,如图4-31所示形式是正确的嵌套形式:

img127

图4-31 循环的嵌套

【例4.19】输出九九乘法表。

img128

运行结果如图4-32所示。

img129

图4-32

分析:

第5、6、7行为乘法表的表头部分,直接用printf()函数输出即可。输出九九乘法表是有规律的,一共有9行,外层循环控制行数见第8行代码,外层循环的循环体中首先需要输出i的值,然后再用一个循环控制输出乘积部分见第11行至13行代码。特别注意第13行代码,缺少后将导致无法换行。

【例4.20】输出500~600以内的所有素数。

img130

运行结果如图4-33所示。

img131

图4-33

分析:

素数即数学中的质数,判断一个数n是否为素数,最基本的方法是采用试除法(从2开始一直除到n-1)。其实可以采用更加简便的方法来提高程序的效率。由相关数学知识可知:只需除到n的平方根即可判断出该数是否为素数。程序的外层循环中num的值从500变化到600,内层循环进行试除即第9行代码。flag为一个标记,初值为0,如果找到一个因子立即修改flag的值为1,经过内层循环之后,如果flag的值仍为0说明该数是素数,输出该数并对计数器变量count的值加1。为了使每行只输出6个素数,故使用了第18、19行来进行换行控制。

【例4.21】利用循环的嵌套输出如下图形。

img132

img133

运行结果如图4-34所示。

img134

图4-34

分析:

该图形一共有7行,可以分成两部分来处理。题目中将前4行看成一个整体,后3行看成一个整体。输出前4行如5~12行代码所示,外层循环控制行数,经分析可知数量关系如下:

img135

显然,空格数与行数的关系为j=4-i,字母数与行数的关系为k=2*i-1。第7、8行输出空格,9、10行输出字母,第11行换行。同理,后3行也可以按照这个方法来分析。

思考:读者可以试试用一个大的循环嵌套结构来输出这个图形,即不分割成两个图形,直接用外层循环控制7行的输出。

4.3.8 循环和数组

(1)使用循环语句遍历数组元素

由于数组中包含若干个元素,利用循环可以非常快捷的访问每个元素,包括给元素赋值或者输出数组中元素的值。由于数组的下标是从0开始的,因此在使用循环时,循环变量的初值应从0开始。

【例4.22】使用循环语句遍历一维数组。

img136

运行结果如图4-35所示。

img137

图4-35

分析:

程序中定义了一个包含10个元素的整型数组arrayNum,并且对其初始化。第6行使用for循环来输出每个元素的值,特别需要注意的是循环变量i的初值赋值为0,循环结束的条件是i<10而不是i<=10,取到等号将导致数组下标越界。

【例4.23】使用循环语句遍历二维数组。

img138

运行结果如图4-36所示。

img139

图4-36

分析:

用循环遍历二维数组中的元素需要使用循环的嵌套结构,外层循环控制数组行下标的变化(第6行),内层循环控制数组列下标的变化(第8行)。这里特别注意第10行语句的位置,它是负责输出一行后换行。

(2)循环和数组的应用

数组的引入使得处理大批量数据变得非常方便,再加上与循环结构的完美结合,在实际生活中有很多应用,如数据的查找、排序等等。下面来看几个具体应用。

【例4.24】输出斐波那契数列的前30项。斐波那契数列的规律如下:

img140

img141

img142

运行结果如图4-37所示。

img143

图4-37

分析:

第4行定义一个含有30个元素的整型数组F,初始化前两项为1,后面的28项通过第6行的循环来进行赋值。第8行至第13行按照每行6个数据进行格式化输出。

下面的例题是关于排序的例子。排序即将一组无序数据按照从小到大(或从大到小)的顺序进行排列。最常用的排序算法有冒泡排序和选择排序等,首先介绍一下算法的基本原理。

①冒泡排序

冒泡排序也称交换排序,一列数据中相邻数据按照一种有序的方式进行交换。冒泡排序一共需要若干趟比较,每一趟又需要若干次比较。例如,现在要对M个数据进行降序排列,第一趟比较要找出最小的数据,具体做法为将M个数据中相邻的两个数进行比较,如果第一个数小于第二个数则交换两数位置,经过第一趟比较之后该数列中最小的数据已经排在末位。接下来进行第二趟比较,但是不必考虑末位的那个数据,只需将前面的M-1个数据中每相邻的两个数进行比较,将较小的数往后交换,经过第二趟比较之后找出来了次小的数,按照这个思想继续比较。算法总结如下,M个数据排序共需要M-1趟比较,由于第一趟比较中比较了M-1次,第二趟比较了M-2次,显然第i趟需要比较M-i次。结合程序设计,可以利用循环的嵌套控制比较的趟数和次数。

作为冒泡排序过程中的一趟比较,以下面5个数据降序排序为例,第一趟比较和交换的过程如图4-38所示。

img144

图4-38 冒泡排序中第一趟比较

【例4.25】利用冒泡排序算法对10个整数进行降序排序后输出。

img145

运行结果如图4-39所示。

img146

图4-39

分析:

题目中要求对10个整数进行排序,故根据冒泡排序思想一共需要9趟比较,外层循环控制趟数见代码第6行,第7行控制每一趟比较的次数。若想改为升序排列只需要将第9行改为大于号即可。第10至14行是交换两个整数的方法,排序完毕用第16行的循环输出结果。

②选择排序

选择排序也是常用的一种排序算法,其算法的基本思想为:首先从待排序数据中(假设共N个数据)找到最小的数据,将这个最小值与待排序数据列表中的第一个数据交换位置。接着找出剩下的N-1个数据中最小的数据与列表中的第二个数据交换位置,依此继续下去。显然N个数据需要重复N-1次上述过程。举例如图4-40所示。

img147

图4-40 选择排序

【例4.26】利用选择排序算法对10个整数进行升序排序后输出。

img148

img149

运行结果如图4-41所示。

img150

图4-41

分析:

第6行的for循环语句控制比较的趟数。变量minindex记录最小值的下标。第10至17行是寻找最小值,第18至23行是将最小值与相应位置的元素进行交换。