下一类控制流语句是无条件跳转(unconditional jump)。无条件跳转使执行流程跳转到代码中的另一位置;所谓“无条件”,即跳转每次必定发生(与 if
或 switch
仅在条件满足时才跳转不同)。
在 C++ 中,无条件跳转通过 goto
语句实现,跳转目标由语句标签(statement label)指定。与 switch
的 case
标签类似,语句标签通常不缩进。
示例:
#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
此例还说明:
- 语句标签必须关联一条语句,故在函数末尾使用空语句
;
。 - 由于标签具有函数作用域,无需前向声明即可跳转。
- 该写法并不推荐——用
if
语句跳过打印更清晰。
跳转限制
- 只能在同一函数内跳转(不能跨函数)。
- 向前跳转时,不得跳过仍在作用域内且已初始化的变量定义。例如:
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 的朋友
最佳实践
除非替代方案显著降低可读性,否则应避免使用 goto
语句。