跳转语句
跳转语句共有三种,分别称为 break 语句、continue 语句和 goto 语句。
break 语句
你在 switch 语句章节已经见过了 break 语句。它的写法就是:
break;
除了之前你学过的用法外,break 语句还可以出现在 for 语句和 while 语句的循环体内。它的含义是跳出循环。也就是说,当程序执行到 break 语句的时候,会跳出一层循环,不再执行循环体内的代码,也不再进行下次循环,直接执行循环之后的语句(若有)。
举个例子来说,需要编写这样一个程序:允许用户输入 10 个整数,一旦输入的数等于 42,就输出 Yes
后程序终止、不再接受输入;若这十个数都不等于 42,就直接终止程序。这个问题可以通过调整循环的条件来实现,但是写起来会很不方便:因为循环的停止取决于两者:一是已经输入的数的个数,二是输入的数是否为 42。在这种情况下,break 语句就能派上用场:
这段代码中,for 循环的条件由输入数的个数来控制。为了保证输入 42
的时候也能跳出循环,于是加上了第 7 行所述的 if 语句。在 if 语句的分支中,首先输出,随后执行 break 语句,就可以实现循环的退出。
下面这张图出自国内的教材,尽管不够准确,但也可以较为清晰地指出 break 语句的作用。
continue 语句
continue 语句与 break 语句相似,也只能用于循环体内。它的写法很简单:
continue;
为了解释它的含义,考虑这样一个问题:和我们之前经常看到的例题一样,仍然是输入 n 个数并求和输出;但这次要求只对正整数求和,如果输入负数就忽略它。那么你可以写出像右侧这样的程序:在 for 语句里嵌套一个 if 语句,使得只有当输入的数是正数的时候才加到 sum
里去。
没错。但除此之外,运用 continue 语句的话就还有另一种写法。这段代码是说:若 x
小于等于零,则执行 continue 语句。什么意思呢?continue 语句的含义是忽略循环体的剩余部分,直接进入下一次循环。因此在这里,如果 x
小于等于零,我就忽略这一次执行求和的部分:因为它是一个非正数,不是我们想要的数。但不同于 break,continue 仍然会执行下一次循环,于是下次输入仍然会进行。
注意,continue 语句忽略的是循环体的剩余部分,而 for 循环的迭代表达式的运算仍然会进行。在上个例子中,即便 sum += x;
这条语句不会执行,但 i++
这个计数用的表达式仍然会被运算。这就保证了允许输入的数仍然是 n 个。
那么有的读者可能会有疑问,明明原先用一个 if 语句就能解决的问题,为什么要通过“反过来”用 continue 语句取消的方法来解决呢?这是因为,当使用 continue 语句之后,原来的主体代码“层级变深了”。在解决现实问题的程序中,经常会出现多个不合法的输入需要跳过,这个时候如果用 if 语句的话会写成:
for (...) { if (条件1) { if (条件2) { if (条件3) { 这些条件都满足就做... } } } }
这个时候,你会发现需要做的代码进入了一个非常“深”的区域,稍不留意就容易因为忽略了大括号而导致逻辑上的错误。即便用逻辑表达式把这些 if 的条件并起来,也会变成一个超级长的、不易阅读的表达式。
continue 语句可以很好地解决这个问题。刚才的代码,完全等价于:
for (...) { if (!条件1) continue; if (!条件2) continue; if (!条件3) continue; 这些条件都满足就做... }
你会发现主题代码仍然保持在很“浅”的层次上,也不会因为缩进、大括号的问题而出现误判。所以目前人们认为,使用 continue 语句是一个良好的编程习惯。
最后仍然用教科书上的图示来总结 continue 语句的含义:
goto 语句
先把话题回到 break 语句上。在之前没有特意强调一点,就是 break 语句只能跳出一层循环。超过一层的循环,break 语句无能为力。比如我想要在以个数组中找到出现两次的元素,找到后立即结束程序,那么我首先将代码写成这样:
但接下来,如果我在最里层写 break 的话,它只能跳出里面那层 for 循环。造成的结果是,外层的 for 循环仍然会继续,并没有实现我们“停止程序”的目标。因此在这种情况下最方便的写法就是 goto 语句了。
goto 语句长成这样:
goto 标号;
其中 标号
我们已经在 switch 语句中见过了。但是这里的标号可以是任意的名字、也可以标在复合语句中的任何位置上:
{ 一些语句... 标号: 一些语句... }
其中 标号
的命名规则与变量名是一致的。整个 goto 语句的含义就是:无条件跳转到 标号
所在的位置上。
那么刚才的问题运用 goto 语句就可以这样写:
这里将 outer
标号标在输出语句上,它在最外层循环的后面。而第 8 行的 goto 语句中指出,当执行这一行语句的时候,直接跳转到第 12 行执行。这就实现了跳出两层循环。
goto 语句是非常自由的;它甚至可以完全实现循环的效果,比如:
就可以实现一个简单的循环。
goto 语句来自于汇编语言中的
jmp
指令。
正因为 goto 语句太过自由,所以学术上曾经因为 goto 语句的去留进行过一场大论战。其中:
- 正方认为应该废除 goto 语句,原因有:
- 包含 goto 语句的程序难以阅读,难以查错;
- 去掉 goto 语句后,可以直接从程序结构上反映程序的运行过程,而且也有利于程序正确性的证明。
- 反方认为 goto 语句应该保留,原因是:
- goto 语句使用起来比较灵活,而且有些情形能够提高程序的效率;
- 如果一味强调删除 goto 语句,有些情形反而会使程序过于复杂,增加一些不必要的计算量。
我们在这里不做展开,但给出如下建议:追求最简洁和最易读的写法。