第3.10章: goto

English

goto 语句

goto 语句直接跳转到同一函数中的某个标签。 它不检查条件,也不遵循正常的控制流。


goto label;

label:
    statement;

标签是由标识符加冒号构成的。 它们必须与 goto 位于同一函数中。

基础示例


print("A\n");
goto skip;
print("B\n");   // skipped

skip:
print("C\n");

这会打印:


A
C

向前与向后跳转

goto 既可以向前跳,也可以向后跳。

向前跳


goto end;

print("never printed\n");

end:
print("done\n");

向后跳(类似循环)


::std::uint_least32_t i{};

start:
println("i=", i);
++i;
if (i != 5) {
    goto start;   // jump back
}

限制:不能跳入作用域

goto 语句不能跳入某个会跳过自动变量初始化的作用域。 这可以防止使用未初始化的变量。


goto label;

{
    ::std::int_least32_t x{42};   // initialization skipped
label:
    println(x);                   // illegal: x not initialized
}

跳出作用域是允许的;析构函数仍会正常执行。

限制:不允许在 constexpr 函数中使用

goto 不能出现在 constexpr 或 consteval 函数中。 这些函数必须具有严格结构化的控制流。


constexpr int f(int x) {
    if (x == 0)
        goto fail;   // error: goto not allowed in constexpr
    return x;
fail:
    return -1;
}

我们将在第7章:编译期编程中详细讲解 constexpr 函数。

这项限制正是为 C++ 添加带标签的 break 与 continue 的提案动机之一: 它们提供了在 constexpr 上下文中允许的结构化多层退出, 而 goto 不允许。

合理用例:跳出多层循环

goto 可以一次退出多层嵌套循环。 这有时比使用标志变量更清晰。


for (::std::uint_least32_t i{}; i != 3; ++i) {
    for (::std::uint_least32_t j{}; j != 3; ++j) {
        if (i == 1 && j == 1) {
            goto done;   // exit both loops
        }
        println("i=", i, ", j=", j);
    }
}

done:
println("finished");

为什么应当少用 goto

goto 让控制流更难跟踪。 现代 C++ 提供了更安全的替代方案:

只在 goto 能提升清晰度时才使用它。

伪图:goto


          ┌───────────────┐
          │   normal flow  │
          └───────┬───────┘
                  │
                  ├──▶ goto label ───────────────▶ label:
                  │
                  ▼
          ┌───────────────┐
          │   next stmt    │
          └───────────────┘
      

历史警示:Dijkstra 对 goto 的批评

在一篇著名的文章中,计算机科学家 Edsger W. Dijkstra 认为 goto 让程序更难理解、更难推理。 他的批评在于:任意跳转会破坏程序的结构化流程, 使人难以看清控制权如何在代码中移动。

这一论点深刻影响了现代编程语言的设计,包括 C++——它鼓励使用 循环、if 语句和函数来构成结构化的控制流,而不是无限制的跳转。

Dijkstra 的批评在今天依然适用:只有当 goto 确实能提升清晰度时才应使用它, 当结构化替代方案能更清楚地表达意图时,就应当避免它。

要点