第6章 循环控制

视频讲解:61分钟

在日常的生活中会有许多简单和重复的工作,为完成这些必要的工作会花费很多时间,而编写程序就是为了使工作变得简单,使用计算机来解决这些重复的工作是非常好的选择。

本章致力于使读者了解循环语句的特点,分别介绍3种循环结构:while语句结构、do-while语句结构和for语句结构,并且对这3种结构进行区分讲解,使读者掌握转移语句的有关内容。

学习摘要:

 循环语句的概念

 while循环语句的使用方式

 do-while循环语句的使用方式

 for循环语句

 区分3种循环语句的各自特点和嵌套使用方式

 使用转移语句控制程序的流程

6.1 循环语句

视频讲解

在第5章的介绍中可以了解到,程序在运行时可以通过判断、检验条件做出选择。程序除了可以做出抉择外,还必须能够重复,也就是反复执行一段指令,直到满足某个条件为止。例如,要计算一个公司所有项目的消费总额,就要将所有的消费加起来。

这种重复的过程就称为循环。C语言中有3种循环语句:while、do-while和for循环语句。循环结构是结构化程序设计的基本结构之一,因此熟练掌握循环结构是程序设计的基本要求。

6.2 while语句

视频讲解

动图演示

使用while语句可以执行循环结构,其一般形式如下:

while (表达式) 语句

其语句执行流程图如图6.1所示。

while语句首先检验一个条件,也就是括号中的表达式。当条件为真时,就执行紧跟其后的语句或者语句块。每执行一遍循环,程序都将回到while语句处,重新进行检验是否满足条件。如果一开始就不满足条件的话,则跳过循环体里的语句,直接执行后面的程序代码。如果第一次检验时满足条件,那么在第一次或其后的循环过程中,必须有不满足条件的操作,否则,循环无法终止。

图6.1 while语句执行流程

说明

无法终止的循环常常被称为死循环或者无限循环。

例如,下面的代码:

在这段代码中,while语句首先进行判断iSum变量是否小于常量100,如果iSum变量小于100为真,那么执行紧跟着后面的语句块。如果iSum变量小于100为假,那么跳过语句块中的内容直接执行下面的程序代码。在语句块中,可以看到对其中的变量进行加1的运算,这里的加1运算就是循环结构中使条件为假的操作,也就是使得iSum不小于100,否则程序会一直循环下去。

【例6.1】 计算1累加到100的结果。(实例位置:资源包\源码\06\6.1)

本实例计算1~100所有数字的总和,使用循环语句可以将1~100的数字进行逐次加运算,直到while判断的条件不满足为止。

运行程序,显示效果如图6.2所示。

程序代码如下:

代码分析:

(1)在程序代码中,因为要计算1~100的累加结果,所以要定义两个变量,iSum表示计算的结果,iNumber表示1~100的数字。为iSum赋值为0,iNumber赋值为1。

(2)使用while语句判断iNumber是否小于等于100,如果条件为真,则执行下面语句块中的内容;如果条件为假,则跳过语句块执行后面的内容。初始iNumber的值为1,判断的条件为真,所以执行语句块。

(3)在语句块中,总和iSum等于之前计算的总和加上现在iNumber表示的数字,完成累加操作。iNumber++表示自身加1操作,语句块执行结束,while再次判断新的iNumber值。也就是说,iNumber++这条语句是可以使循环停止的操作。

(4)当iNumber大于100时,循环操作结束,将结果iSum进行输出。

【例6.2】 使用while为用户提供菜单显示。(实例位置:资源包\源码\06\6.2)

在使用程序时,根据程序的功能会有许多选项,为了使用户可以方便地观察到菜单的选项,要将菜单进行输出。在本实例中,利用while将菜单进行循环输出,这样可以使用户更为清楚地知道每一个选项所对应的操作。

运行程序,显示效果如图6.3所示。

图6.2 计算1累加到100的结果

图6.3 使用while为用户提供菜单显示

程序代码如下:

代码分析:

(1)在程序代码中,定义的变量iSelect是用来保存菜单的输入选项的变量。使用while语句检验iSelect变量,语句“iSelect!=0”表示如果iSelect不等于0。当条件为真时,执行其后的语句块中的内容;当条件为假时,执行后面的代码return 0程序结束。

(2)因为设定iSelect变量的值为1,所以while语句检验条件为真,执行其中的语句块。在语句块中首先显示菜单,将每一项的操作都进行说明。

(3)使用scanf语句,将用户要选择的项目输入。之后使用switch语句判断变量,根据变量中保存的数据,按检验出的结果进行对应的操作,其中每一个case会输出不同的菜单功能提示信息。default默认情况为,当用户输入的选项为菜单所列以外选项时的操作。

(4)显示的菜单中有4项功能,其中的选项0为退出。那么输入0时,iSelect保存0值。这样在执行完case为0的情况后,当while再检验iSelect的值时,判断的结果为假,那么不执行循环操作,执行后面的代码后,程序结束。

6.3 do-while语句

视频讲解

动图演示

有些情况下,不论条件是否满足,循环过程必须至少执行一次,这时可以采用do-while语句。do-while语句的特点就是先执行循环体语句的内容,然后再判别循环条件是否成立。其一般形式如下:

其语句执行流程图如图6.4所示。

do-while语句是这样执行的,首先执行一次循环体语句中的内容,然后判别表达式,当表达式的值为真时,返回继续执行循环体语句,直到表达式的判断为假时为止,此时循环结束。

说明

while语句和do-while语句的区别在于:while语句在每次循环之前检验条件,do-while语句在每次循环之后检验条件。这也可以从两种循环结构的代码上看出来,while结构的while语句出现在循环体的前面,do-while结构中while语句出现在循环体的后面。

图6.4 do-while语句执行流程

例如,下面代码所示:

代码分析:

在上面的代码中,首先执行iNumber++的操作,也就是说不管iNumber是否小于100都会执行一次循环体中的内容。然后判断while后的括号中的内容,如果iNumber小于100,则再次执行循环语句块中的内容,条件为假时执行下面的程序操作。

注意

在使用do-while语句时,条件要放在while关键字后面的括号里,最后必须加上一个分号,这是许多初学者容易忘记的。

【例6.3】 使用do-while语句计算1到100之间的累加结果。(实例位置:资源包\源码\06\6.3)

在6.2节中,计算1到100之间所有数字的累加结果使用的是while语句,在本实例中使用do-while语句可以实现相同的功能,在程序运行的过程中,虽然两者的结果是相同的,但是要了解其中操作过程的不同之处。

运行程序,显示效果如图6.5所示。

图6.5 使用do-while语句计算1到100之间的累加结果

程序代码如下:

代码分析:

(1)在程序中,同样定义iNumber表示1到100间的数字,iSum表示计算的总和。

(2)do关键字之后是循环语句,语句块中进行累加操作,并对iNumber变量进行自加操作。语句块的下面是while语句检验条件,如果检验为真,继续执行上面的语句块操作;如果检验为假,程序执行下面的代码。

(3)在循环操作完成之后,将结果输出。

6.4 for语句

视频讲解

动图演示

在C语言中,使用for语句也可以用来控制一个循环,并且在每一次循环时修改循环变量。在循环语句中,for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,还可以用于只给出循环结束条件而循环次数不确定的情况。下面将对for语句的循环结构进行详细的介绍。

6.4.1 for语句使用

for语句的一般形式如下:

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

每条for语句包含3个用分号隔开的表达式。这3个表达式用一个对圆括号括起来,其后紧跟着循环语句或语句块。当执行到for语句时,程序首先计算第一个表达式的值,接着计算第二个表达式的值。如果第二个表达式的值为真,程序就执行循环体的内容,并计算第三个表达式。得到结果然后再检验第二个表达式,执行循环,如此反复,直到第二个表达式的值为假,则退出循环。

其语句执行流程图如图6.6所示。

通过上面的流程图和对for语句的介绍,总结其执行过程如下:

(1)先求解表达式1。

(2)求解表达式2,若其值为真,则执行for语句中的循环语句块,然后执行第(3)步。若为假,则结束循环,转到第(5)步。

(3)求解表达式3。

(4)回到上面的第(2)步骤继续执行。

(5)循环结束,执行for语句下面的一个语句。

for语句简单的应用形式如下:

图6.6 for语句执行流程

for(循环变量赋初值;循环条件;循环变量) 语句块;

例如,实现一个循环操作:

在上面的代码中,表达式1处是对循环变量i进行赋值操作,然后表达式2处是进行判断循环条件是否为真。因为i的初值为1,所以小于100,执行语句块中的内容。表达式3处是每一个次循环后,对循环变量的操作,然后再判断表达式2处的状态。为真时,继续执行语句块;为假时,循环结束,执行后面的程序代码。

【例6.4】 使用for语句显示随机数。(实例位置:资源包\源码\06\6.4)

在本实例中,要求使用for循环语句显示10个随机数字,其中产生随机数要使用到srand函数和rand函数,这两个函数都包括在stdio.h头文件中。

运行程序,显示效果如图6.7所示。

图6.7 使用for语句显示随机数

程序代码如下:

代码分析:

(1)在程序代码中,定义变量counter。在for语句中先对counter进行赋值,之后判断counter<10的条件是否为真,然后根据判断的结果选择是否执行循环语句。

(2)srand和rand函数都包含在stdio.h头文件中,srand函数的功能是设定一个随机发生数的种子,rand函数是根据设定的随即发生数种子产生特定的随机数。

(3)循环语句中使用srand函数设定counter+1为设定的种子,然后使用rand函数产生特定的随机发生数。使用printf函数将产生的随机数进行输出。

说明

如果在使用rand函数之前不提供种子值,也就是不用srand函数进行设定种子值。则rand函数总是默认1作为种子,每次将产生同样的随机数序列。因此在本例中,每次循环使用counter+1作为种子值。

对于for语句的一般形式也可以使用while循环的形式进行表示:

上面就是使用while语句表示for语句的一般形式,其中的表达式对应着for语句括号中的表达式,下面通过一个实例来看一下这两种操作。

【例6.5】 使用while语句模仿for语句的一般形式。(实例位置:资源包\源码\06\6.5)

在本实例中,使用for语句先实现一个由循环功能完成的操作,之后再使用while语句实现相同的功能。在实例中要注意的是,for语句中的表达式与while语句中的表达式所对应的位置。

运行程序,显示效果如图6.8所示。

图6.8 使用while语句模仿for语句的一般形式

程序代码如下:

代码分析:

(1)在程序中,还是定义变量iNumber表示1~100的数字,不过刚开始没有为其赋值,iSum表示计算的结果。

(2)使用for语句执行循环操作,在括号中第一个表达式位置处,为循环变量进行赋值。第二个表达式判断条件。条件为真,执行语句块中内容;条件为假,不进行循环操作。

(3)在循环语句块中,进行累加运算。之后执行for括号中的第三个表达式,对循环变量进行自增操作。循环操作后,将保存有计算结果的变量iSum进行输出。

(4)在使用while之前要将变量恢复的值。iNumber=1就相当于for语句中第一个表达式的作用,为变量设置初值。之后在while括号中的表达式iNumber<=100与for语句中第二个表达式相对应。最后iNumber++自加操作与for语句括号中的最后一个表达式相对应。

6.4.2 for循环的变体

通过上面的学习知道,for语句一般形式中有3个表达式。在实际程序的编写过程中,这3个表达式可以根据情况进行省略,接下来分别进行讲解。

 for语句中省略表达式1

for语句中第一个表达式的作用是对循环变量设置初值。因此,如果省略了表达式1的话,就会跳过这一步操作,则应在for语句之前给循环变量赋值。例如:

for(;iNumber<10;iNumber++)

注意

省略表达式1时,其后的分号不能省略。

【例6.6】 省略for语句中的表达式1。(实例位置:资源包\源码\06\6.6)

在本实例中,同样实现1到100间数字的累加计算,不过将for中的表达式1省略。

运行程序,显示效果如图6.9所示。

程序代码如下:

图6.9 省略for语句中的表达式1

代码分析:

在代码中可以看到for语句中将表达式1省略,而在定义iNumber变量时直接为其赋初值。这样在使用for语句循环时就不用为iNumber赋初值,从而省略了表达式1。

 for语句中省略表达式2

如果表达式2省略,即不判断循环条件,循环无终止地进行下去。也就是默认为表达式2始终为真。例如:

在括号中,表达式1为赋值表达式,而表达式2是空缺的,这样就相当于使用while语句:

注意

一定要注意,如果表达式2为空缺的话,将会是无限循环。

 for语句中省略表达式3

表达式3也可以省略,但此时程序设计人员应该另外设法保证循环能正常结束,否则程序会无终止地循环下去。例如:

 3个表达式都省略

这种情况既不设置初值,也不判断条件,也没有改变循环变量的操作,则会无终止地执行循环体,例如:

这种情况相当于while永远为真的情况:

 表达式1为与循环变量赋值无关的表达式

表达式1可以是设置循环变量初值的赋值表达式,也可以是与循环无关的其他表达式。例如:

6.4.3 for语句中的逗号应用

在for语句中的表达式1和表达式3处,除了可以使用简单的表达式外,还可以使用逗号表达式。即包含一个以上的简单表达式,中间用逗号间隔。例如在表达式1处为变量iCount和iSum设置初始值:

或者执行两次循环变量的自加操作:

表达式1和表达式3都是逗号表达式,在逗号表达式内按照自左至右顺序求解,整个逗号表达式的值为其中最右边的表达式的值。例如上面:

for(iCount=1;iCount<100;iCount++,iCount++)

相当于:

for(iCount=1;iCount<100;iCount=iCount+2)

【例6.7】 计算1到100间所有偶数的累加结果。(实例位置:资源包\源码\06\6.7)

在本实例中,为变量赋初值的操作都放在for语句中,并且对循环变量进行两次自加操作,这样所求出的结果就是所有的偶数和。

运行程序,显示效果如图6.10所示。

程序代码如下:

图6.10 计算1到100间所有偶数的累加和

代码分析:

在程序代码中,for语句首先对变量iSum、iCount进行初始化赋值。每次循环语句执行完后进行两次iCount++操作,最后将结果输出。

6.5 3种循环语句的比较

视频讲解

前面介绍了3种可以执行循环操作的语句,这3种循环都可以用来处理同一问题。一般情况下它们可以相互代替。下面是这3种循环语句的比较。

 while和do-while循环,只在while后面指定循环条件,在循环体中应包含使循环趋于结束的语句(如i++或者i=i+1等)。for循环可以在表达式3中包含使循环趋于结束的操作,可以将循环体中的操作全部放在表达式3中。因此for语句的功能更强,只要用while循环能完成的,都能用for循环实现。

 用while和do-while循环时,循环变量初始化的操作应在while和do-while语句之前完成。而for语句可以在表达式1中实现循环变量的初始化。

 while循环、do-while循环和for循环,都可以用break语句跳出循环,用continue语句结束本次循环(break和coutinue语句在本章后面进行介绍)。

6.6 循环嵌套

一个循环体内又包含另一个完整的循环结构,就称之为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。不管在什么语言中关于循环嵌套的概念都是一样的。

6.6.1 循环嵌套的结构

while循环、do-while循环和for循环之间可以互相嵌套。例如,下面几种嵌套方式都是正确的。

 while结构中嵌套while结构

 do-while结构中嵌套do-while结构

 for结构中嵌套for结构

 do-while结构中嵌套while结构

 do-while结构中嵌套for结构

以上是关于一些嵌套的结构方式,当然还有不同结构的循环嵌套,在此就不一一列举,读者只要将每种循环结构的使用方式把握好,就可以正确地写出循环嵌套。

6.6.2 循环嵌套实例

在本节中会讲解循环嵌套的实例,使读者了解循环嵌套的使用方法。

【例6.8】 使用嵌套语句输出金字塔形状。(实例位置:资源包\源码\06\6.8)

在本实例中,利用嵌套循环输出金字塔形状。那么显示一个三角形要考虑这样三点,首先要控制输出三角形的行数,其次控制三角形的空白处,最后是三角形的显示输出。

运行程序,效果如图6.11所示。

图6.11 使用嵌套语句输出金字塔形状

程序代码如下:

代码分析:

在代码中可以看到,首先通过一个循环控制三角形的行数,也就是三角形的高度。然后在循环中嵌套循环语句,控制每一行输出的空白和输出*号的数量,这样就可以将整个金字塔的形状进行输出。

技巧

设计显示三角形,可以将过程想象成先显示一个倒立的直角三角形(由空格组成),然后再输出一个正立的三角形。

6.7 转移语句

视频讲解

转移语句包括goto语句、break语句、continue语句。这3种语句使得程序的流程按照这3种转移语句的使用方式进行改变。下面将对这3种语句的使用方式进行详细的介绍。

6.7.1 goto语句

goto语句为无条件转移语句,可以使程序立即跳转到函数内部的任意一条可执行语句。goto关键字后面带一个标识符,该标识符是同一个函数内某条语句的标号。标号可以出现在任何可执行语句的前面,并且以一个冒号“:”作为后缀。一般形式如下:

goto标识符;

goto后的标识符就是要跳转的位置,当然这个标识符要在程序的其他地方给出,但是要在函数内部。函数的内容将会在后面章节介绍,在此有个印象即可。例如:

上面代码中,goto后的Show为跳转的标识符,而下面的“Show:”代码表示goto语句要跳转的位置。这样在上面的语句中第一个printf函数不会执行,而会执行第二个printf函数。

注意

跳转的方向可以向前,也可以向后;可以跳出一个循环,也可以跳入一个循环。

【例6.9】 使用goto语句从循环内部跳出。(实例位置:资源包\源码\06\6.9)

本程序要求在执行循环操作的过程中,当用户输入退出指令后,程序跳转到循环外部显示退出提示的语句之前。

运行程序,显示效果如图6.12所示。

程序代码如下:

图6.12 使用goto语句从循环内部跳出

代码分析:

(1)程序运行时,for循环控制程序步骤。此时循环步骤为1。信息提示用户输入数字,其中0表示退出,99表示下一个步骤。

(2)在for循环中使用do-while判断用户输入,当条件为假时,循环结束,执行for循环的下一步。假如用户输入数字为3,既不退出也不进行下一步骤,程序提示用户继续输入数字。当输入数字为99时,进行下一步,程序显示提示信息“The step is 2”。

(3)如果用户输入的是0,那么通过if语句进行判断为真,此时goto语句将执行跳转操作。其中exit为跳转的标识符。循环的外部使用“exit:”表示goto跳转的位置。程序输出提示信息表示运行结束。

6.7.2 break语句

有时会遇到这样的情况,不考虑表达式检验的结果而强行终止循环,这时可以使用break语句。break语句终止并跳出循环,继续执行后面的代码。break语句的一般形式如下:

break;

break语句不能用于循环语句和switch语句之外的任何语句中。例如在循环语句中使用break语句:

在代码中,虽然while语句是一个条件永远为真的循环,但是在其中使用break语句会使得程序流程跳出循环。

注意

这个break语句和switch…case分支结构中的break语句作用是不同的。

【例6.10】 使用break语句跳出循环。(实例位置:资源包\源码\06\6.10)

使用for语句执行10次循环输出的操作,在循环体中判断输出的次数。当循环变量为5次时,使用break语句跳出循环,终止循环输出操作。

运行程序,显示效果如图6.13所示。

程序代码如下:

图6.13 使用break语句跳出循环

代码分析:

变量iCount在for语句中被赋值为0,因为iCount<10,则执行10次循环。在循环语句中使用if语句判断当前iCount的值。当iCount值为5时,if判断为真,使用break语句跳出循环。

6.7.3 continue语句

在某些情况下,程序需要返回到循环头部继续执行,而不是跳出循环。continue语句的一般形式如下:

continue;

其作用就是结束本次循环,即跳过循环体中下面尚未执行的部分,接着执行下一次的循环操作。

注意

continue语句和break语句的区别是:continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。

【例6.11】 使用continue结束本次的循环操作。(实例位置:资源包\源码\06\6.11)

本实例与使用break语句结束循环的实例相似,不同之处在于将使用break语句的地方改写成了continue,因为continue语句是结束一次循环,所以剩下的循环还是会继续执行。

运行程序,显示效果如图6.14所示。

程序的代码如下:

图6.14 使用continue结束本次的循环操作

代码分析:

通过程序的显示结果可以看到,在iCount等于5时通过调用continue语句,使得当次的循环结束。但是循环本身还没有结束,所以会继续执行。

6.8 实战

视频讲解

6.8.1 爱因斯坦阶梯问题

爱因斯坦著名的阶梯问题是这样的:有一条长长的阶梯。如果你每步跨2阶,那么最后剩1阶;如果你每步跨3阶,那么最后剩2阶;如果你每步跨5阶,那么最后剩4阶;如果你每步跨6阶,那么最后剩5阶;只有当你每步跨7阶时,最后才正好走完,一阶也不剩。请问这条阶梯至少有多少阶?(求所有3位阶梯数)运行结果如图6.15所示。(实例位置:资源包\源码\06\实战\01)

本实例关键是如何来写if语句中的条件,如果这个条件大家能够顺利地写出,那整个程序也基本上完成了。条件如何来写,这主要是根据题意来分析,“每步跨2阶,那么最后剩1阶……当每步跨7阶时,最后才正好走完,一阶也不剩”从这几句可以看出题目的规律就是:总的阶梯数对每步跨的阶梯数取余,得到的结果就是剩余阶梯数,这5种情况是&&的关系,也就说必须同时满足。

图6.15 阶梯问题

6.8.2 斐波那契数列

斐波那契数列的特点是:第一、二两个数为1、1。从第3个数开始,该数是前两个数之和。求这个数列的前30个元素。运行结果如图6.16所示。(实例位置:资源包\源码\06\实战\02)

分析题目中数列的规律,可以用如下等式来表示斐波那契数列:

图6.16 斐波那契数列

这里面将F的下标看成数组的下标即可完成该程序的设计。

6.8.3 银行存款问题

假设银行当前整存零取五年期的年利息为2.5%,现在某人手里有一笔钱,预计在今后的五年当中每年年底取出1000元,到第五年的时候刚好取完,计算在最开始存钱的时候要存多少钱?(实例位置:资源包\源码\06\实战\03)

在分析这个取钱和存钱的过程时,可以采用倒推的方法。如果第五年年底连本带利取出1000元,则要先求出第五年年初的存款,然后再递推第四年、第三年……的年初银行存款数:

第五年年初存款=1000/(1+0.025)

第四年年初存款=(第五年年初存款+1000)/(1+0.025)

第三年年初存款=(第四年年初存款+1000)/(1+0.025)

第二年年初存款=(第三年年初存款+1000)/(1+0.025)

第一年年初存款=(第二年年初存款+1000)/(1+0.025)

运行程序,得到效果如图6.17所示。

图6.17 银行存款问题

6.8.4 计算学生的最高分

假设一个班中有20个学生,输入某科考试的成绩,然后统计出最高分。程序的运行效果如图6.18所示。(实例位置:资源包\源码\06\实战\04)

本程序的解决方法就是首先设置一个初始最大值,然后每次输入一个学生的成绩,与这个最大值进行比较,如果大于初值,则将当前值赋给这个最大值,一直到所有的成绩都读取结束。

图6.18 计算学生的最高分

6.8.5 统计不及格的人数

结合前面的例子,假设一个班中有20个学生,输入某科考试的成绩,然后统计出该班不及格的学生人数。程序的运行效果如图6.19所示。(实例位置:资源包\源码\06\实战\05)

图6.19 统计不及格的人数