我也在微软的一本书中读到同样的内容,在第一页上写着永远不要使用GOTO语句进行编程。使用goto语句会使程序结构混乱,使其非常难以理解。
这总是让我想起一个问题,那就是如果我们被告知永远不要使用GOTO语句,那么为什么它成为许多常见编程语言的一部分呢?
对于一些编程语言(显然不包括我设计的任何一种),比如C和C++,我认为使用goto是因为:
最常见的一个例子就是在C语言中进行嵌套错误/资源管理时,goto
通常被认为是好的选择。例如,Linux内核是一个相当大且成功的C项目,它使用goto
来处理这个问题。
您被告知不要使用goto,因为他们希望教您如何编写代码。但有时候goto可能会很有用。
当您想要立即从多级循环中跳出时,可以为每个步骤添加退出条件,并且可以使用goto endloop。看下面的例子:(伪代码)
while(cond1){
while(cond2){
while(cond3){
if(want to break){
goto endloop
}
do something
if(want to break2){
goto endloop
}
}
do something
}
do something
}
endloop:
do something else
没有使用goto时,它可能看起来像这样:
while(cond1 && exitloopflag){
while(cond2 && exitloopflag){
while(cond3 && exitloopflag){
if(want to break){
exitloopflag = true;
break;
}
do something
if(want to break2){
exitloopflag = true;
break;
}
}
if(exitloopflag)
break;
do something
}
if(exitloopflag)
break;
do something
}
do something else
因此,我们可以争论哪种代码的可读性更好...
goto
的意义很小,有些像人类的阑尾,它的功能已经被条件和循环控制结构(if-then-else, for/while/loops, switch/case语句等)所替代。在C和C++等语言中,它仍然存在于一些极端情况下非常有用,例如跳出深层嵌套的循环:for (...)
{
for (...)
{
for(...)
{
...
// hit a fatal error, need to break out to outermost scope
goto whoopsiedoodle;
}
}
}
whoopsiedoodle:
...
然而,由于以下一些非常好的原因,建议不要使用它:由于它可以在函数中分支到任何一个方向,所以它可能会通过简单的检查破坏调试代码的能力。例如,给定以下片段:
i = 1;
label: printf("i = %d\n", i);
变量i
会打印什么值?这个打印语句会被执行多少次?在你解决每一个goto label;
的情况之前,你无法知道。现在,想象一下代码结构类似于以下内容:
i = 0;
goto label;
foo: ...
...
i = 1;
label: printf("i = %d\n", i);
...
goto foo;
...
...
都有几十甚至几百行代码。还要想象到另外10或11个标签散布在各处,并且与goto
语句随机分布。这是我早期职业生涯遇到的一些真实代码的模型。调试这样的代码唯一的方法是逐行追踪执行,考虑沿途的每个goto
。这个代码本来就写得很糟糕(一个5000行的单片式main
函数,成百上千个单独的变量,有些在文件作用域中声明,有些在main
中局部声明,以及其他可怕的事情),但使用goto
是一个力量放大器,将仅仅糟糕的代码变成了无法维护的淤泥。这个代码定义了“脆弱”,我们实际上不能更改它的任何一行而不破坏其他东西。goto
也会妨碍编译器优化代码的能力。现代编译器非常聪明,并且可以利用结构化编程技术进行有效的分支预测或展开循环以获得实际的性能增益。像上面那样的代码几乎不可能被编译器优化。上面片段所模拟的真实世界代码?我们尝试使用优化标志(gcc -O1)编译它。编译器吃掉了所有可用的RAM,然后吃掉了所有可用的交换空间,导致内核崩溃。goto
可以有效地使用,只要遵守以下规则:if
,for
,while
或switch
条件)。
3.避免跨越大段代码进行分支。goto
在Java中明确不被允许。它在其他语言中存在是因为它作为一条简单的机器码指令易于实现,而不是因为这是一个好主意。
顺便说一下,在Java中可以做类似于goto
的事情。看看你是否能够轻松地理解这个简单的例子是做什么的,如果你无法理解,那么你就回答了自己的问题。如果你很清楚这个例子的作用和工作原理,你必须考虑到许多人会觉得这很困惑。
FOUND: {
for(String s: list)
if(s.contains(like))
break FOUND;
System.out.println(like + " not found");
}
跳转语句模拟机器代码;机器代码中的流控制结构非常有限,没有等同于goto语句的话就无法编写重要的机器代码程序。
最初的高级语言,比如COBOL和FORTRAN,有goto语句,因为人们知道从使用自己的机器码来控制它们的流程。 C语言也是这样创建的,虽然是几年之后才创建的,部分原因是该语言特意创建为易于编译成高效的机器代码 - 一些C语言结构,例如增量/减量运算符,存在于其机器代码中。可以说C不是高级语言,而是结构化汇编语言(这没有什么不好,它领先于时代,并且仍然非常有用)。
迪克斯特拉和其他人发现(1)如果您拥有足够的更高级别的流控制结构,则不需要goto语句,(2)goto语句容易出错。我记得阅读过对代码进行分析的结果,发现goto语句出错的可能性比任何其他类型的语句高9倍。
这是一种具有讽刺意味的情况,因为从概念上理解这个语句并不困难;只是程序员过于频繁地使用它,往往会使整个程序变得更难理解。
Goto被允许在我们跳出一个作用域时使用,
由于有几种方法可以打破作用域,例如break、break label、return等,因此没有必要使用纯粹的goto(除非一些早期语言不支持上述某些方法,通常是break label)。
换句话说,goto并没有消失(编写程序时改变执行顺序是不可避免的),我们只是通过使用不同的关键字来区分不同类型的执行路径。现在goto有特殊情况,有自己的关键字。这也使代码更易于理解。
goto
命令,你就不会看到任何警告不要使用它。 - Kerrek SBgoto
实际上非常重要。避免在C中使用goto
的唯一方法是使用非常小的函数并进行特定的清理跳转。如果您愿意,C++通过析构函数提供了"自动goto",这就是为什么它允许您编写任意代码并且不会显得混乱。 - Kerrek SB