第3.10章: goto
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++ 提供了更安全的替代方案:
while、for、do whilebreak与continueswitch- 函数与提前返回
只在 goto 能提升清晰度时才使用它。
伪图:goto
┌───────────────┐
│ normal flow │
└───────┬───────┘
│
├──▶ goto label ───────────────▶ label:
│
▼
┌───────────────┐
│ next stmt │
└───────────────┘
历史警示:Dijkstra 对 goto 的批评
在一篇著名的文章中,计算机科学家 Edsger W. Dijkstra 认为
goto 让程序更难理解、更难推理。
他的批评在于:任意跳转会破坏程序的结构化流程,
使人难以看清控制权如何在代码中移动。
这一论点深刻影响了现代编程语言的设计,包括 C++——它鼓励使用
循环、if 语句和函数来构成结构化的控制流,而不是无限制的跳转。
Dijkstra 的批评在今天依然适用:只有当 goto 确实能提升清晰度时才应使用它,
当结构化替代方案能更清楚地表达意图时,就应当避免它。
要点
- goto 跳转到标签:没有条件,没有检查。
- 标签必须在同一函数内。
- 不能跳入作用域:不能绕过变量初始化。
- constexpr 中禁止 goto:要求结构化的控制流。
- 带标签 break 的动机:constexpr 安全的多层退出。
- 谨慎使用:优先采用结构化的控制流。
- Dijkstra 的警告:无限制跳转损害可读性,并使代码更难推理。