为什么不使用GOTO语句?

6
我正在攻读软件工程硕士学位。从大专到大学,我的老师和导师告诉我在编程语言中永远不要使用GOTO语句。根据D. Sundar的《软件工程》一书所述:

使用goto语句会使程序结构混乱,使其非常难以理解。

我也在微软的一本书中读到同样的内容,在第一页上写着永远不要使用GOTO语句进行编程。
这总是让我想起一个问题,那就是如果我们被告知永远不要使用GOTO语句,那么为什么它成为许多常见编程语言的一部分呢?

7
由于人类因素原理:如果任何编程语言中都没有 goto 命令,你就不会看到任何警告不要使用它。 - Kerrek SB
8
在C语言中,如果你想写具有多个退出点的任意函数,goto实际上非常重要。避免在C中使用goto的唯一方法是使用非常小的函数并进行特定的清理跳转。如果您愿意,C++通过析构函数提供了"自动goto",这就是为什么它允许您编写任意代码并且不会显得混乱。 - Kerrek SB
1
历史原因。如果你今天从头开始发明一种语言,它不会有“goto”。但是当C语言正在开发时,这还不是很清楚,而C++之所以有它,是为了保持与C的兼容性。 - James Kanze
@KerrekSB 在C语言中,没有任何情况下使用“goto”是合理的。如果你觉得需要使用“goto”,那么你的函数可能过于冗长和复杂。 - James Kanze
2
可能是为什么“使用Goto”是糟糕的编程?的重复问题。 - H H
显示剩余7条评论
10个回答

8

对于一些编程语言(显然不包括我设计的任何一种),比如C和C++,我认为使用goto是因为:

  1. 它可以紧密地模拟硬件实际能够执行的操作,这也是这些语言的目标。
  2. 有时候它确实很有用,比其他“结构化”方法更好。

最常见的一个例子就是在C语言中进行嵌套错误/资源管理时,goto通常被认为是好的选择。例如,Linux内核是一个相当大且成功的C项目,它使用goto来处理这个问题。


显然,这取决于情况,并且比更加结构化的方法更有用和更好。那么为什么我一遍又一遍地听到来自在良好组织工作的专业人士从不使用goto语句的建议呢? - Usman YousafZai
@MuhammadUsman 因为经常会有害处,而且细节可能会逃脱老师的注意,或者很难向学生解释清楚。 - spbnick
@MuhammadUsman 因为你掌握了多种方法来完成某件事情,也就是拥有了经验,所以总会有更好的方法。 - Peter Lawrey

3

您被告知不要使用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

因此,我们可以争论哪种代码的可读性更好...


3
现代编程语言中存在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可以有效地使用,只要遵守以下规则:
1.仅向前分支。 2.永远不要跳过控制结构的主体(即不要绕过ifforwhileswitch条件)。 3.避免跨越大段代码进行分支。

3

3

Java中没有goto语句。在Java关键字列表中,指定了goto关键字,但标记为“未使用”。


2
我想解释一下,为什么你应该非常谨慎地使用GOTO语句。
我并不是说GOTO语句完全是坏的,这也是它仍然被实现在C++中(由于兼容性原因,也实现在C中)的原因之一,在JAVA中它只是一个保留关键字。
我可以给你展示一些例子,其中GOTO是最好的解决方案(在我看来)。
但是你应该防止使用它,原因如下:
(摘自书籍Ohne C Zu C++,经我翻译和修改)
想象一下你玩像《卡坦岛拓荒者》这样的游戏,无论你去哪里,每个人都有自己的规则。这没关系,你只需要学习规则,然后就可以和他们一起玩了。
但是如果他们不教你规则,而只是使用它呢?你很快就会失去与他们一起玩的乐趣。
goto语句就像一个新的《卡坦岛拓荒者》规则。
编程已经足够难以理解了,当你可以可靠地假设你从顶部开始,一次执行一行,向下走,一行一行走时。使用goto语句会打破这种假设,突然间,“游戏”变得不确定了。这是一个新的、不确定的规则。

1

goto在Java中明确不被允许。它在其他语言中存在是因为它作为一条简单的机器码指令易于实现,而不是因为这是一个好主意。

顺便说一下,在Java中可以做类似于goto的事情。看看你是否能够轻松地理解这个简单的例子是做什么的,如果你无法理解,那么你就回答了自己的问题。如果你很清楚这个例子的作用和工作原理,你必须考虑到许多人会觉得这很困惑。

FOUND: {
    for(String s: list)
        if(s.contains(like))
             break FOUND;
    System.out.println(like + " not found");
}

1

在一些语言中,它被用于异常处理(例如VBA)

Java将goto作为关键字,但它没有实际作用。我相信他们这样做是为了防止你将任何东西命名为goto

然而,在某些情况下,goto可能会很有用!


1

跳转语句模拟机器代码;机器代码中的流控制结构非常有限,没有等同于goto语句的话就无法编写重要的机器代码程序。

最初的高级语言,比如COBOL和FORTRAN,有goto语句,因为人们知道从使用自己的机器码来控制它们的流程。 C语言也是这样创建的,虽然是几年之后才创建的,部分原因是该语言特意创建为易于编译成高效的机器代码 - 一些C语言结构,例如增量/减量运算符,存在于其机器代码中。可以说C不是高级语言,而是结构化汇编语言(这没有什么不好,它领先于时代,并且仍然非常有用)。

迪克斯特拉和其他人发现(1)如果您拥有足够的更高级别的流控制结构,则不需要goto语句,(2)goto语句容易出错。我记得阅读过对代码进行分析的结果,发现goto语句出错的可能性比任何其他类型的语句高9倍。

这是一种具有讽刺意味的情况,因为从概念上理解这个语句并不困难;只是程序员过于频繁地使用它,往往会使整个程序变得更难理解。


1

Goto被允许在我们跳出一个作用域时使用,

  • 就在该作用域结束之后。
  • 其他情况都是无效的(跳进一个循环、跳进if块的中间、跳进另一个函数等)。

由于有几种方法可以打破作用域,例如breakbreak labelreturn等,因此没有必要使用纯粹的goto(除非一些早期语言不支持上述某些方法,通常是break label)。

换句话说,goto并没有消失(编写程序时改变执行顺序是不可避免的),我们只是通过使用不同的关键字来区分不同类型的执行路径。现在goto有特殊情况,有自己的关键字。这也使代码更易于理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接