08-循环
循环
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. [^1]
目录
[TOC]
循环
本章将介绍 C 语言的重复语句(迭代语句),这种语句允许用户设置循环。
循环(loop)时重复的执行其他语句(循环体)的一种语句。在 C 语言中,每个循环都有自己的控制表达式(controlling expression)。循环每执行一次,都要对控制表达式求值。如果表达式为真(表达式的值非 0),那么循环继续执行;否则,退出循环。
C 提供了三种迭代语句:
- while 语句
- do 语句(do while 语句)
- for 语句
其中以 for 语句最为常用。
本节后面还会讨论循环相关的 C 语言特性。如 break,continue,goto语句
一 while 语句
1. while 的基本用法
while 的基本格式如下:
while(控制表达式){ |
执行 while 语句时,首先计算控制表达式的值。如果值不为零,那么执行循环体,接着再次判定 控制表达式的真值,如果为真,再次执行循环体。直到控制表达式的真值为假,才会结束 while 语句。
**例1-1:**倒计数程序
int i = 10; |
关于这个例子,我们可以对 while 进行深度思考:
-
while 循环终止时,控制表达式的值一定为假。
int i = 10;
while(i > 0){
printf("%d\n", i);
i--;
}
printf("%d", i); // i is now 0 -
可能根本不执行 while 循环体。
int i = 0;
while(i > 0){
printf("%d\n", i);
i--;
}
// Nothing is printed. -
while 语句常有多种写法
int i = 10;
while(i > 0){
printf("%d\n", i--); // 将 i-- 写在 printf 内,简化循环
}
2. 无限循环
如果控制表达式的值始终非零,while 循环将无法终止。
while(1){ |
除非循环体内有控制循环的语句(break,return,goto)或者调用了导致程序终止的函数,非则上面的循环永远不会结束。
程序 1:显示平方表
现编写一个程序来显示平方表。首先程序提示用户输入一个数 n,然后显示出 n 行的输出,每行包含 一个 1 ~ n 的数及其平方值。
This program prints a table of squares. |
参考答案:
|
程序 2:数列求和
This program sums a series of integers. |
参考答案:
|
我写的,仅供参考:
|
或者 while 循环可以这样写:
int n = 1, sum = 0; |
二 do 语句
1. do while 基本用法
do 语句 和 while 语句其实本质上是相同的。只不过 do 语句至少会执行一次循环体。
基本形式:
do{ |
例2-1倒计数程序
int i = 10; |
顺便一提,do 语句最好都加上花括号。
虽然 do while 没有 while 语句使用的那么多,但是前者对于至少需要执行一次的循环来说是十分方便的。
程序 3:编写一个程序计算用户输入的整数的位数
Enter a integer: 60 |
参考答案:
|
如果我们用 while 循环:
while(n != 0){ |
如果你输入的是 0 ,这个循环会直接退出。这不符合我们的预期,0 也是整数啊,而且它有 1 位数。
三 for 循环
现在开始介绍 C 语言最后一种循环,也是功能最强大的一种循环:for 语句。它是我们用的最多的一种循环,一定要熟练掌握。
1. for 语句的基本用法
for(表达式1; 表达式2; 表达式3){ |
例3-1 倒计数程序
for(i = 10; i > 0; i--){ |
在执行上面这个 for 语句时,i 先初始化为 10;然后判定 i 是否大于 0 ;因为结果为真,执行循环体;然后对变量 i 进行自减操作;然后再次判断 i 是否大于 0 … 直到最后一次 i 自减后,i > 0 不成立了,退出循环。
for 循环如果我们用 while 语句 也可以模拟:
i = 10; |
抽象一下即为:
表达式1; |
for 循环中的 i--
可以写成 --i
吗?
即:
for(i = 10; i > 0; --i){ |
这种做法对循环没有任何影响。i++
和 i--
在 for 循环的 表达式3 中是完全等价的。
2. 在 for 语句中省略表达式
1.省略第一个表达式
i = 10; |
注意:
- 省略第一个表达式相当于不初始化 i
- 即便 第一个表达式省略了,也不能省略后面的分号
2. 省略第三个表达式
for(i = 10; i > 0;){ |
3. 省略第一个和第三个表达式
i = 10; |
是不是很像我们的 while 语句?
4. 省略第二个表达式
for (; ;) { |
省去控制表达式,默认为真,因此 for 语句会不断循环下去。
它相当于:
while (1) { |
3. C99 中的 for 语句
C99 中第一个表达式可以替换成一个声明:
for(int i = 0; i < 10; i++){ |
变量 i 不需要在该句之前进行声明。事实上,如果变量 i 在之前已经声明了,这个语句会创建一个新的 i 且该值仅用于循环内。(新的 i 会覆盖之前的 i 。for 语句的花括号相当于一个块,新 i 的作用域仅在这个块内。)
例如:
int main(void) { |
输出:
0 |
最后输出的 20 不符合我们的预期,这说明了一个问题:
旧的 i 始终存在,只不过在 for 语句内,新的 i 相对旧的 i 显“显性”(可以用高中生物的基因来理解一下;或者你就理解为覆盖,但是新的 i 开辟的是新的内存,它不会真的覆盖旧的 i )。
for 语句声明的变量不可以在循环外访问(生存期仅在 for 语句内)。例如:
for (int i = 0; i < 10; i++) { |
让 for 语句声明自己的循环控制变量通常是一个好办法:这样方便且程序的可读性更强,但是如果在 for 循环退出后还要使用该变量,则只能使用以前的 for 语句格式。
另外,for 语句可以声明多个变量,只要它们的类型相同:
for(int i = 0, j = 10; i < j; i++, j--){ |
4. 逗号表达式
逗号表达式(comma expression)
基本用法
表达式1,表达式2,表达式3,...,表达式n; |
- 逗号表达式的优先级低于所有其他运算符
- 从左向右依次执行:先计算表达式1,然后是表达式2 …
- 最终整个逗号表达式的值为最后一个表达式的值
例如:
i = 1, j = 2, k = i + j; |
相当于是:
((i = 1), (j = 2), (k = i + j)); |
整个逗号表达式的值为 k = i + j 的值,也就是 3
应用
假如在进入 for 语句时希望初始化两个变量
sum = 0; |
改写为:
for(sum = 0, i = 1; i <= N; i++){ |
程序 4:显示平方表(for)
|
程序 5:不用乘法,实现程序“显示平方表”
|
四 退出循环
有时,循环还没有结束,但我们需要在循环的中间设置退出点,甚至可能需要对循环设置多个退出点。break 语句可以满足这种需求。
continue 语句用来跳过某次迭代的部分内容,但是不会跳过整个循环。
goto 语句允许程序从一条语句直接跳到另一条语句。
1. break 语句
break 语句不但可以把程序控制从 switch 语句中转移出来;还可以用于跳出 while,do while,和 for 循环。
程序 6:判断素数
假如要编写一个程序判断 n 是否为素数。我们计划是编写一个 for 语句用 n 除以 2 到 n - 1 之间的所有数。一旦发现有约数就跳出循环,没不需要继续尝试下去。循环终止后,可以用 if 判断循环是提前终止(不是素数)还是正常终止(是素数)。
|
程序 7:程序2 重写
程序 2 中,要求我们当用户输入 0 时,结束求和。我们用很多中方法去实现了,我们可以用 break 语句让程序的意图更加直观。
|
一个 break 只能跳出一个循环,如果循环嵌套,那么可能需要多个 break 才能从里层的循环跳出。
int i; |
运行一下这个程序,发现,屏幕上一直在输出 5,说明外层的循环没有跳出。
2. continue 语句
break 语句刚好将程序控制转移到循环体的末尾之后;而 continue 语句刚好将程序控制转移到循环体末尾之前。
break 语句会跳出循环;而 continue 语句会将程序控制留在循环体之内。
其实 continue 的作用就是跳过本次循环点剩下内容,重新开始下一轮循环。
程序 8:计算奇数和
编写一个程序。先提示用户输入一个数 n,然后将 1 ~ n 的所有奇数相加,然后输出最终的和
不使用 continue
|
使用 continue
只需要改变 for 循环即可
for(int i = 1; i <=n; i++){ |
3. goto 语句
break 和 continue 语句都是跳转语句:它们可以把控制从程序的一个位置转移到另一个位置。然而,这两者都是受限制的。
goto 语句则可以跳转到函数中任何有标号的语句处。(C99 增加了一条限制:goto 不能用于绕过变长数组(后面的章节会讲)的声明。)
标识符:语句; |
程序:程序 6 重写
|
goto 语句在早期的编程语言中很常见,但是日常 C 语言编程却很少使用它了。break,continue,return 语句(本质上都是受限制的goto 语句)和 exit 函数足以应付大多数需要使用 goto 的情况。
而且 goto 语句不建议滥用,能不用就不用,因为 goto 语句可以打乱程序原本的执行顺序,这大大降低了程序的可读性,提高了改错成本。(这里程序圆是有切身体会的,大一 做C语言的课设的时候,那时候对 C 掌握的并不是很好。我在一个函数内大量的使用 goto 语句,导致我最后都不知道我的 goto 跳到哪里了。)
但是 goto 语句偶尔还是有用的,比如上面的例子:循环嵌套的情况,使用goto 语句就很好跳出多重循环了:
int i; |
程序:账薄结算
这个程序帮你理解一种简单的交互式程序设计,我们可以通过这种方式设计菜单。
题目:
开发一个程序用来维护账簿的余额。程序将为用户提供选择菜单:清空余额账户,向账户存钱,从账户取钱,显示当前余额,退出程序。选项用 0,1,2,3,4表示。程序的会话类似这样:
**** ACME checkbook-balancing program **** |
参考程序:
|
五 空语句
i = 0; ; j = 1; |
第二个语句就是一个空语句:除了末尾的分号,什么符号也没有。
空语句主要有一个好处:编写空循环体的循环。
判断素数的循环我们可以这样改写:
for(d = 2; d <= n && n % d != 0; d++) |
注意不要写成:
for(d = 2; d <= n && n % d != 0; d++) ; |
程序员习惯将空语句单独放在一行,否则,一般人阅读程序时可能会混淆后面的语句是否是其循环体。
for(d = 2; d <= n && n % d != 0; d++) ; |
一般情况下,将普通循环转化为带空循环体的语句不会提高效率,但是会让程序更加简洁。
但是在一些情况下,可能带空循环体的循环会更加高效。如:读取字符数据时(后面会讲)。
空语句可能会造成的情况
1.if 语句
if(d == 0); |
如果输入的 d 不是 0,后面的这条消息一样会被打印。
2.while 语句
i = 10; |
程序会进入死循环,因为 while 没有循环体,而 i 的的值不会减小。
i = 11; |
循环终止,但是花括号内的语句只被执行一次。
输出:0
3. for 语句
for(i = 10; i > 0; i--); |
printf 语句被执行一次
输出:0
参考资料:《C Primer Plus》《C语言程序设计:现代方法》
[^1]: 用100个函数操作一个数据结构比仅用10个函数但是操作10个不同的数据结构要好。Epigrams on Programming 编程警句