改变循环流程的两种方式
循环的条件决定它何时结束,但有时真正的答案出现在循环体中间。你在数组里查找某个元素,第三个就找到了,何必再检查剩下的 997 个?又或者你在处理一个列表,其中一项是空的:跳过它,继续处理。
C 语言为这两种情况各准备了一个关键字:
break:立即离开循环。程序从循环之后的第一条语句继续执行。continue:只放弃这一轮,进入下一轮。
第一个循环打印 1 2 3 4 后就停止了。第二个打印 1 2 3 4 6 7 8 9 10:它跳过了一个值,然后继续执行。
循环中的 break
最典型的用法是一找到目标就停止的查找:
因为 found 在 break 之前就被赋值了,循环之后的代码可以区分“找到了”和“元素都查完了”这两种情况。结果变量加 break 的组合,就是 C 语言中标准的查找写法。如果循环计数器是在循环头里声明的,循环结束后就不能再用它,因为它在右花括号处就离开了作用域。
break 每次只跳出一层:离它最近的 for、while、do-while 或 switch。
循环中的 continue
continue 用于那些应该跳过、但不应该中止整个处理的元素:
continue 跳到哪里,取决于循环的类型:
for -> the update part of the header (so the counter still advances)
while -> the condition test (nothing else in the body runs)
do-while -> the condition test at the bottom
这个区别很实际。在 for 循环中,continue 永远不会跳过计数器自增,因为自增写在循环头里。在 while 循环中却会:
/* BUG: continue skips i++, so this hangs at the first even number */
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
i++;
}
要么把 i++ 移到 continue 之前,要么改写成 for 循环,循环头的结构让你不可能写错。
switch 中的 break 不会跳出循环
break 结束的是离它最近的循环或 switch,看哪个更近。把 switch 放进循环里,switch 中的每个 break 都属于 switch:
q 命令打印了它的消息,但循环继续处理了 cc。如果“退出”本应结束循环,这个 break 并没有做到。解决办法有三种:标志变量、goto,或者把循环移到函数里用 return,下面会一一演示。
跳出嵌套循环
一个 break 只跳出一层。在两层嵌套循环中,它只结束内层循环,外层循环会像什么都没发生一样开始下一轮。
C 语言没有 break 2,也没有带标签的 break。有三种正当的做法:
标志变量。 可移植,一目了然,只是稍显啰嗦:
代价是标志变量要出现在两个地方:外层条件和赋值处。嵌套到三层时,这种写法就真的很难受了。
goto。 C 语言中饱受诟病的跳转语句,而这正是有经验的 C 程序员会理直气壮地使用它的场景:
向前 goto 到循环之后的标签,效果和其他语言中带标签的 break 完全一样,只用一行,也不需要维护标志变量。goto 的坏名声来自向后跳转和跳进其他代码块中间,那些确实会让控制流难以阅读。从嵌套循环中向前跳出一次并不属于这种情况。Linux 内核在清理路径中也使用同样的模式。要有意识地、节制地使用它;别因为忌讳 goto,就写出一个用三个标志变量、更难读懂的替代方案。
函数加 return。 通常是最好的答案,因为一次查找值得拥有一个名字:
return 会一次性退出函数中的所有循环,不需要标志变量,也不需要标签,而且查找代码现在有了名字,可以单独测试。当嵌套深到跳出它都成了问题时,这往往说明这段代码本来就该写成一个函数。
什么时候有帮助,什么时候有害
当 break 和 continue 能让循环在顶部一次性说明“这种情况处理完了”,而不必把整个循环体包进一个 if 时,它们就物有所值。对比一下:
/* with continue: the real work is not indented */
for (int i = 0; i < n; i++) {
if (!isValid(a[i])) continue;
if (isDuplicate(a[i])) continue;
process(a[i]);
}
/* without: every guard adds a level */
for (int i = 0; i < n; i++) {
if (isValid(a[i])) {
if (!isDuplicate(a[i])) {
process(a[i]);
}
}
}
用得太多时,它们就有害了。一个散落着五个 break 和三个 continue 的循环,它的退出条件没有写在任何地方,读代码的人必须在脑中把整个循环体模拟一遍,才知道它什么时候停止。两条经得起考验的准则:把 continue 守卫语句集中放在循环体顶部;如果一个循环有一两个以上的 break,想一想循环头里的条件是不是本该承担这份工作。
还有两个小陷阱。在 do-while 中,continue 跳到底部的条件判断,而那本来就是接下来要执行的部分:无害,但常常让人意外。另外,在任何循环或 switch 之外使用 break 是编译错误,这是本文所有错误中唯一一个编译器能替你发现的。
常见问题
C 语言中 break 和 continue 有什么区别?
break 直接结束整个循环,程序从循环之后的语句继续执行。continue 只放弃当前这一轮,进入下一轮:在 for 循环中跳到更新部分,在 while 或 do-while 中直接跳到条件判断。
C 语言怎么跳出嵌套循环?
一个 break 只能跳出最内层的循环。要同时跳出两层,可以设置一个标志变量并在外层循环的条件里检查它,可以用 goto 跳到循环之后的标签,或者把循环移到一个函数里再 return,这通常是最清晰的做法。
break 跳出的是循环还是 switch?
跳出离它最近的那一层。位于循环内部 switch 里的 break 只结束 switch,循环会继续执行。要在 switch 内部跳出循环,需要标志变量、goto 或 return。
为什么 continue 让我的 while 循环变成了死循环?
因为 continue 会跳到条件判断,跳过循环体剩下的部分,如果计数器自增写在末尾,它也会被跳过。在 for 循环 中更新部分写在循环头里,所以总会执行;在 while 循环中,把更新移到 continue 之前,或者改用 for。