章节目录

goto语句

本节阅读量:

我们将讨论的下一种控制流语句是无条件跳转。无条件跳转会导致执行跳转到代码中的另一个位置。术语“无条件”意味着跳转总是发生(不像if语句或switch语句,跳转仅根据表达式的结果有条件地发生)。

在C++中,无条件跳转是通过goto语句实现的,跳转到的位置是通过使用语句标签来标识的。下面是goto语句和语句标签的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <cmath> // 引用 sqrt() 函数

int main()
{
    double x{};
tryAgain: // 这是标签语句
    std::cout << "Enter a non-negative number: "; 
    std::cin >> x;

    if (x < 0.0)
        goto tryAgain; // 这是goto语句

    std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
    return 0;
}

在此程序中,要求用户输入一个非负数。如果输入负数,程序将使用goto语句跳回tryAgain标签。然后再次要求用户输入新数字。这样,我们可以不断地要求用户输入,直到输入有效的内容。

下面是该程序的示例运行:

1
2
3
Enter a non-negative number: -4
Enter a non-negative number: 4
The square root of 4 is 2

标签语句具有函数作用域

在前面,我们讨论了两种作用域:局部(代码块)作用域和文件(全局)作用域。标签语句使用第三种作用域:函数作用域,这意味着标签在整个函数中都是可见的,甚至在其声明点之前也是可见的。goto语句及其对应的标签语句必须出现在同一函数中。

虽然上面的示例显示了向后跳转(到函数中的前一点)的goto语句,但goto语句也可以向前跳转:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

void printCats(bool skip)
{
    if (skip)
        goto end; // 向前跳转; 标签 'end' 可以访问到,因为它是函数作用域
    
    std::cout << "cats\n";
end:
    ; // 标签语句必须要一个语句相关联上
}

int main()
{
    printCats(true);  // 跳过了cout语句,因此不会打印
    printCats(false); // 打印 "cats"

    return 0;
}

这会打印:

1
cats

除了向前跳转外,上面的程序中还有几个值得一提的细节。

首先,注意标签语句必须与某条语句相关联(标签必须标记一条语句)。因为函数末尾没有语句,所以这里必须使用空语句。其次,由于标签语句具有函数作用域,即使end还没有声明,也能够跳到标有end的语句,不需要对标签语句进行前向声明。第三,值得明确指出的是,上面的程序风格很差——用if语句跳过print语句会比用goto语句更好。

goto有两个主要限制:只能在单个函数内跳转(不能从一个函数跳到另一个函数),并且如果向前跳转,不能跳过变量的显式初始化。例如:

1
2
3
4
5
6
7
8
int main()
{
    goto skip;   // error: 这个跳转是非法的...
    int x { 5 }; // 因为这里的显式初始化会被跳过
skip:
    x += 3;      // 这里的x的初始值设置的是多少?
    return 0;
}

请注意,可以向后跳转;当再次执行后续的初始化语句时,变量会被重新初始化。


避免使用goto

在C++(以及其他现代高级语言)中,应避免使用goto。著名计算机科学家 Dijkstra 在一篇著名但难读的论文《Go To Statement Considered Harmful》中阐述了避免使用goto的理由。goto的主要问题是它允许程序员在代码中随意跳转。这会产生一种不太好听地被称为“意大利面条代码”的东西。意大利面条代码的执行路径就像一碗意大利面条一样纠结扭曲,使得代码逻辑极难阅读。

正如Dijkstra有点幽默地说的那样,“程序员的质量是他们编写的程序中go-to语句密度的反比函数”。

几乎所有使用goto语句编写的代码,都可以用C++中的其他控制流(如if语句和循环)更清楚地表达。一个值得注意的例外是,当您需要退出嵌套循环但不想退出整个函数时,使用goto跳转到循环之后的位置,可能是最简洁的解决方案。


8.5 switch fallthrough机制与作用域

上一节

8.7 循环和while语句简介

下一节