goto 语句

下一类控制流语句是无条件跳转(unconditional jump)。无条件跳转使执行流程跳转到代码中的另一位置;所谓“无条件”,即跳转每次必定发生(与 ifswitch 仅在条件满足时才跳转不同)。

在 C++ 中,无条件跳转通过 goto 语句实现,跳转目标由语句标签(statement label)指定。与 switchcase 标签类似,语句标签通常不缩进。

示例:

#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;
}

程序不断要求用户输入,直到输入非负数才计算平方根。

运行示例:

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

语句标签具有函数作用域

在第七章“对象作用域”中,我们介绍了局部(块)作用域和文件(全局)作用域。语句标签引入了第三种作用域:函数作用域,即标签在整个函数内均可见,甚至早于其声明点。goto 语句及其对应标签必须在同一函数内。

上例展示了向后跳转;goto 也可向前跳转:

#include <iostream>

void printCats(bool skip)
{
    if (skip)
        goto end;  // 向前跳转;标签 end 因函数作用域而在此处可见

    std::cout << "cats\n";
end:
    ;  // 标签需关联语句,此处用空语句
}

int main()
{
    printCats(true);   // 跳过打印
    printCats(false);  // 输出 cats
    return 0;
}

输出:

cats

此例还说明:

  1. 语句标签必须关联一条语句,故在函数末尾使用空语句 ;
  2. 由于标签具有函数作用域,无需前向声明即可跳转。
  3. 该写法并不推荐——用 if 语句跳过打印更清晰。

跳转限制

  1. 只能在同一函数内跳转(不能跨函数)。
  2. 向前跳转时,不得跳过仍在作用域内且已初始化的变量定义。例如:
int main()
{
    goto skip;   // 错误:跳过了变量 x 的初始化
    int x{ 5 };
skip:
    x += 3;      // 若 x 未初始化,行为未定义
    return 0;
}

向后跳转可以跨越变量初始化;再次执行初始化时变量会重新初始化。

避免使用 goto

在 C++(及其他现代高级语言)中,goto 被视为不良实践。计算机科学家 Edsger W. Dijkstra 在著名论文《Go To Statement Considered Harmful》中阐述了其弊端。主要问题是 goto 允许程序员任意跳转,易形成“意大利面条代码”(spaghetti code)——执行路径如一团纠缠的面条,逻辑难以追踪。

Dijkstra 曾幽默地说:“程序员的水平与其代码中 goto 语句的密度成反比。”

几乎所有使用 goto 的代码都能用 if循环 等结构更清晰表达。唯一例外是:需要从嵌套循环中跳出但不退出整个函数时,goto 可能是最简洁方案。

示例:

#include <iostream>

int main()
{
    for (int i = 1; i < 5; ++i)
    {
        for (int j = 1; j < 5; ++j)
        {
            std::cout << i << " * " << j << " = " << i * j << '\n';
            if (i * j % 9 == 0)
            {
                std::cout << "Found product divisible by 9. Ending early.\n";
                goto end;
            }
        }
        std::cout << "Incrementing the first factor.\n";
    }
end:
    std::cout << "And we're done.\n";
    return 0;
}

作者注:来自 xkcd 的朋友
Code::Blocks安装

最佳实践
除非替代方案显著降低可读性,否则应避免使用 goto 语句。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"