
2.2 循环结构

视频讲解
在现实生活中,如果多次重复做同一件事情,往往会让人感到很懊恼。但在编程中大可不必如此,因为程序员对于计算机来说,就是扮演“上帝”的角色,程序员只需要负责设计规则,而苦的累的差事全部都由计算机来完成。在程序开发中,当需要重复执行同一段代码很多次的时候,可以使用循环结构来实现。
2.2.1 while语句
C语言有三种实现循环结构的语句,首先介绍的是while语句,语法格式为:

while语句的语法非常简单,只要表达式的值为真,那么就会不断执行循环体里边的语句或程序块。它的执行过程的流程图如图2-6所示。

图2-6 while语句流程图
下面通过分析两个例子给大家讲解while语句的用法。
例1:计算1+2+3+…+100的结果,程序流程图如图2-7所示。

图2-7 例1流程图

程序实现结果如下:

代码分析:
(1)程序运行到while语句时,因为变量i初始化的值为1,所以i <= 100这个条件表达式的值为真,执行循环体的程序块;执行结束后变量i的值变为2,sum的值变为1。
(2)接下来会继续判断表达式i <= 100是否成立,因为此时变量i的值是2,所以条件表达式再次成立,继续执行循环体的程序块;执行结束后变量i的值变为3,sum的值变为3。
(3)重复执行步骤(2)。
(4)当循环进行到第100次,变量i的值变为101的时候,变量sum的值即为结果5050。此时i <= 100不再成立,退出循环,转而执行while循环体下边的代码。
例2:统计从键盘输入的一行英文句子的字符个数。
这里需要用到一个新的函数——getchar函数。

getchar函数的功能是从标准输入流(可以暂时理解为键盘)中获取字符。该函数没有参数,如果调用成功,返回获取的字符(用整型表示其ASCII码);如果调用失败,返回值是EOF(通常被定义为-1)。
回忆一下,我们是按下回车键表示输入结束,所以这里判断是否继续循环的条件可以是“getchar() != '\n'”,程序流程图如图2-8所示。

图2-8 例2流程图

程序实现如下:

2.2.2 do-while语句
除了通过while语句实现循环,C语言中还有一个叫do-while的语句,也是用于实现循环。do-while语句的语法格式为:

如果把while语句比喻为一个谨慎的人的话,那么do-while语句就是一个莽撞的人。while语句是先判断表达式,如果表达式结果为真,才执行循环体里边的内容;而do-while则相反,不管三七二十一,先执行循环体的内容再判断表达式是否为真。do-while语句执行过程的流程图如图2-9所示。

图2-9 do-while语句流程图
在有些情况下,这种先执行再判断的循环模式是很有用的。比如,编写一个需要验证用户密码的程序,只有密码正确才能继续执行。如果使用while语句实现,就要写两次让用户输入密码的代码,程序流程图如图2-10所示。而如果换成do-while语句,则只需要写一次,程序流程图如图2-11所示。

图2-10 使用while语句

图2-11 do-while语句
最后还有一点,如果不强调的话肯定一堆初学者会出错:do-while语句在while的后面一定要使用分号(;)表示该语句结束。

程序实现结果如下:

2.2.3 for语句

视频讲解
前面已经介绍了while语句和do-while语句,它们很相似,唯一区别就是条件判断的位置——while在入口处判断,do-while则在出口处判断,所以也把它们称为入口条件循环和出口条件循环,如图2-12所示。

图2-12 入口条件循环和出口条件循环
观察图2-13,通常一个循环都将涉及以下三个动作。

图2-13 循环的基本结构
(1)初始化计数器。
(2)判断循环条件是否满足。
(3)更新计数器。
对于while语句,这些动作是分散在三个不同的地方。如果能把它们都集中起来,那么后期无论是调试也好修改也罢,就便捷了许多,for语句应运而生。for语句的语法格式为:

三个表达式用分号隔开,其中:表达式1是循环初始化表达式;表达式2是循环条件表达式;表达式3是循环调整表达式。
图2-13的例子使用for语句就可以做以下修改:

程序实现如下:

这样一来,for语句将初始化计数器、循环条件判断、更新计数器三个动作组织到了一起,如果以后要修改循环的次数、每次递进的跨度或者循环结束条件,只需要在for语句后边的小括号内统一修改即可。
下面的程序用于判断某个数是否为素数。素数指在大于1的自然数中,除了1和此数自身外,无法被其他自然数整除的数。关于素数的求法有很多,这里采用比较常规的方式:迭代测试从2到num/2的所有整数是否能被整除(num为待测试的整数),如果没有出现能被整除的整数,那么它就是素数。
代码如下:

程序实现如下:

2.2.4 灵活的for语句
for语句也可以“偷懒”,其中的表达式1、表达式2和表达式3可以按照需要进行省略(但分号不能省)。
比如2.2.3节中循环判断素数的代码,就有以下三种“偷懒”的方式。
(1)“偷懒”大法1:for(;表达式2;表达式3)。

(2)“偷懒”大法2:for(表达式1;表达式2;)。

(3)“偷懒”大法3:for(;表达式2;)。

除此之外,C语言还允许for语句的三个表达式均不写(但需要写两个分号),for(; ;)的作用就相当于while(1)。
注意:
如果目的不是特别明确,建议不要随意施展“偷懒”大法,因为程序的可读性会因此降低。
另外,表达式1和表达式3可以是一个简单的表达式,也可以包含多个表达式,只需使用逗号将各个表达式分开即可。请看下面例子:

程序实现如下:

最后提一下C99的新标准:C99允许在for语句的表达式1中定义变量。

程序实现如下:

注意:
在编译时需要加上“-std=c99”,否则可能会出错。
增加这个新特性的原因主要是考虑到循环通常需要一个计数器,而这个计数器出了循环就没什么用了。所以在表达式1的位置定义的变量,活动范围仅限于循环中,出了循环,它就无效了,下面的例子证实了这一点。

试图在循环外使用表达式1定义的变量,会导致错误:

2.2.5 循环结构的嵌套
循环结构跟分支结构一样,也可以被嵌套。对于嵌套的循环结构,执行顺序是从内到外,也就是先执行内层循环,再执行外层循环。
请看下面例子:

程序实现如下:

从变量i和变量j的变化中可以观察到,这是一个3×3的循环,每执行三次内循环,执行一次外循环。
趁热打铁,下面例子打印一个九九乘法表,如图2-14所示。

图2-14 九九乘法表
由图2-14可以看出,乘号左边的数决定了该行有多少列,所以乘号左边的数可以作为外层循环的变量,而右边的数作为内层循环的变量。另外,外层循环每执行一次,需要添加一个换行符。
代码如下:

程序实现如下:

一般来说,在进入循环体之后,需要先执行完循环体的所有内容,再执行下次循环的条件判断。但是有些时候,希望可以中途退出循环,或跳过一些语句。下一节就来讲解两个循环的辅助语句,它们是循环的“左膀右臂”,没有了它们,循环虽然可以执行,但就没那么灵活了。
2.2.6 break语句

视频讲解
第一个要讲的是break语句。在讲解switch语句的时候,我们说switch语句在执行完匹配的case语句后,并不会自动结束,而是一直往下执行,所以就需要使用break语句让它跳出来。
在循环体中,如果想要让程序中途跳出循环,那么同样可以使用break语句来实现。执行break语句,就可以直接跳出循环体。2.2.3节讲了一个用于判断某个数是否为素数的程序,为了讲解方便,此处重新给出:

请问,如果输入的这个num是10,那么需要执行多少次循环?10 / 2=5,也就是i小于5的情况下都会执行循环体的内容,那么初始化i=2,然后每执行一次,循环i加1,所以i依次为2、3、4的时候执行循环体的内容,所以总共执行了3次。那如果num是100000,应该就要执行49998次循环;如果是1×108,那就要99999998次循环。
但是,我们都知道1亿是肯定能被2整除的,所以后边的99999997次循环完全没有意义!因为某个数只要能够被1和本身之外的任何数整除,那它就一定不是素数。在这种情况下,只需要添加一个break语句,就可以使程序的效率提高很多。因为之后测试的数据会比较大(1×108),所以这里声明变量用long long类型,对应的scanf函数和printf函数要将格式化占位符修改为%lld,代码如下:

最后有一点需要注意的是:对于嵌套循环来说,break语句只负责跳出所在的那一层循环,要跳出外层循环则需要再布置一个break语句才行。下面举个例子:

程序实现如下:

如果要让程序在符合j == 3这个条件的时候,立刻退出整个循环体,那么就需要在外层再布置一个break语句:

2.2.7 continue语句
还有一种情况是,当满足某个条件的时候,跳过本轮循环的内容,直接开始下一轮循环,这时应该使用continue语句。当执行到continue语句的时候,循环体的剩余部分将被忽略,直接进入下一轮循环。请看下面例子:

程序实现如下:

第二行是我们输入的内容,第三行是程序输出的内容,当检查到ch存放的值是大写字母'C'的ASCII码时,程序当作什么也没看到,直接忽略了。
对于嵌套循环来说,continue语句跟break语句是一样的:它们都只能作用于一层循环体。
C语言的语法虽然看似简单、朴素,但千万不要因为它简单就小瞧他。正是因为简单,它的灵活性才能被无限地利用和放大。当然也因为简单,很多潜伏的“陷阱”初学者根本毫无察觉,举个例子,很多人认为for语句和while语句完全等价,随时都可以互换,事实上只要稍不留神,问题就出现了。请看下面代码:

如果将上面代码中的for语句改成while语句,有些读者可能会这样改:

看起来似乎没有什么问题,但是程序执行起来却是死循环,电脑进入“发烧状态”。
这是怎么回事呢?因为for语句和while语句执行过程是有区别的,它们的区别在于:在for语句中,continue语句跳过循环的剩余部分,直接回到调整部分;在while语句中,调整部分是循环体的一部分,因此continue语句会把它也跳过。