跳转语句

跳转语句共有三种,分别称为 break 语句、continue 语句和 goto 语句。

break 语句

你在 switch 语句章节已经见过了 break 语句。它的写法就是:

break;

除了之前你学过的用法外,break 语句还可以出现在 for 语句和 while 语句的循环体内。它的含义是跳出循环。也就是说,当程序执行到 break 语句的时候,会跳出一层循环,不再执行循环体内的代码,也不再进行下次循环,直接执行循环之后的语句(若有)。

举个例子来说,需要编写这样一个程序:允许用户输入 10 个整数,一旦输入的数等于 42,就输出 Yes 后程序终止、不再接受输入;若这十个数都不等于 42,就直接终止程序。这个问题可以通过调整循环的条件来实现,但是写起来会很不方便:因为循环的停止取决于两者:一是已经输入的数的个数,二是输入的数是否为 42。在这种情况下,break 语句就能派上用场:

#include <iostream>
using namespace std;
int main() {
    int i, n;
    for (i = 0; i < 10; i++) {
        cin >> n;
        if (n == 42) {
            cout << "Yes" << endl;
            break;
        }
    }
}

这段代码中,for 循环的条件由输入数的个数来控制。为了保证输入 42 的时候也能跳出循环,于是加上了第 7 行所述的 if 语句。在 if 语句的分支中,首先输出,随后执行 break 语句,就可以实现循环的退出。

下面这张图出自国内的教材,尽管不够准确,但也可以较为清晰地指出 break 语句的作用。 break

continue 语句

continue 语句与 break 语句相似,也只能用于循环体内。它的写法很简单:

continue;
#include <iostream>
using namespace std;
int main() {
    int i, n, x;
    int sum{0};
    cin >> n;
    for (i = 0; i < n; i++) {
        cin >> x;
        if (x > 0) {
            sum += x;
        }
    }
    cout << sum << endl;
}

为了解释它的含义,考虑这样一个问题:和我们之前经常看到的例题一样,仍然是输入 n 个数并求和输出;但这次要求只对正整数求和,如果输入负数就忽略它。那么你可以写出像右侧这样的程序:在 for 语句里嵌套一个 if 语句,使得只有当输入的数是正数的时候才加到 sum 里去。

#include <iostream>
using namespace std;
int main() {
    int i, n, x;
    int sum{0};
    cin >> n;
    for (i = 0; i < n; i++) {
        cin >> x;
        if (x <= 0) continue;
        sum += x;
    }
    cout << sum << endl;
}

没错。但除此之外,运用 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 语句的含义:

continue

goto 语句

先把话题回到 break 语句上。在之前没有特意强调一点,就是 break 语句只能跳出一层循环。超过一层的循环,break 语句无能为力。比如我想要在以个数组中找到出现两次的元素,找到后立即结束程序,那么我首先将代码写成这样:

#include <iostream>
using namespace std;

int main() {
    int a[10]{0, 3, 1, 4, 2, 3, 8, 7, 4, 5};
    int i, j;
    for (i = 0; i < 10; i++) {
        for (j = i + 1; j < 10; j++) {
            if (a[i] == a[j]) {
                cout << "Found" << endl;
                // 然后呢?
            }
        }
    }
}

但接下来,如果我在最里层写 break 的话,它只能跳出里面那层 for 循环。造成的结果是,外层的 for 循环仍然会继续,并没有实现我们“停止程序”的目标。因此在这种情况下最方便的写法就是 goto 语句了。

goto 语句长成这样:

goto 标号;

其中 标号 我们已经在 switch 语句中见过了。但是这里的标号可以是任意的名字、也可以标在复合语句中的任何位置上:

{
    一些语句...
标号:
    一些语句...
}

其中 标号 的命名规则与变量名是一致的。整个 goto 语句的含义就是:无条件跳转标号 所在的位置上。

那么刚才的问题运用 goto 语句就可以这样写:

#include <iostream>
using namespace std;

int main() {
    int a[10]{0, 3, 1, 4, 2, 3, 8, 7, 4, 5};
    int i, j;
    for (i = 0; i < 10; i++) {
        for (j = i + 1; j < 10; j++) {
            if (a[i] == a[j]) {
                cout << "Found" << endl;
                goto outer; // 直接跳转——
            }
        }
    }
outer: // ——到标号这里!
    cout << "Exit" << endl;
}

这里将 outer 标号标在输出语句上,它在最外层循环的后面。而第 8 行的 goto 语句中指出,当执行这一行语句的时候,直接跳转到第 12 行执行。这就实现了跳出两层循环。

goto 语句是非常自由的;它甚至可以完全实现循环的效果,比如:

    int i, sum{0};
    i = 1;
loop:
    sum += i;
    i++;
    if (i < 10)
        goto loop;

就可以实现一个简单的循环。

goto 语句来自于汇编语言中的 jmp 指令。

正因为 goto 语句太过自由,所以学术上曾经因为 goto 语句的去留进行过一场大论战。其中:

  • 正方认为应该废除 goto 语句,原因有:
    • 包含 goto 语句的程序难以阅读,难以查错;
    • 去掉 goto 语句后,可以直接从程序结构上反映程序的运行过程,而且也有利于程序正确性的证明。
  • 反方认为 goto 语句应该保留,原因是:
    • goto 语句使用起来比较灵活,而且有些情形能够提高程序的效率;
    • 如果一味强调删除 goto 语句,有些情形反而会使程序过于复杂,增加一些不必要的计算量。

我们在这里不做展开,但给出如下建议:追求最简洁和最易读的写法。

最近更新:
代码未运行