使用goto值得吗?


29

goto普遍不建议这样做。使用此语句是否值得?



1
goto这是CPU最终要做的事情,但是由于目标端没有标记,因此它与人类需要理解和抽象的内容不能很好地配合。与Intercal比较COMEFROM

2
@ThorbjørnRavnAndersen,当人们谈论状态机中的状态转换时,人类还有什么意思?您看不到相似之处吗?这就是“进入给定状态 ”的意思,而其他任何事情都不过是这种琐碎事情的不必要的复杂化。您所说的“无标记”是什么意思?标签就是这样的标记。
SK-logic

@ SK-logic,取决于您将允许goto到达的距离。状态机不需要goto来实现。

@ThorbjørnRavnAndersen当然goto不是必需的。这是用命令式语言实现它们的最合理的方法,因为它是最接近状态转换语义的东西。而且它的目的地可能离源头很远,不会妨碍可读性。我最喜欢的代码示例是DE Knuth对Adventure游戏的实现。
SK-logic

Answers:


49

在Stack Overflow上已经对此进行了数次讨论,Chris Gillum 总结了可能的用法goto

干净地退出功能

通常在函数中,您可能会分配资源,并且需要在多个位置退出。程序员可以通过将资源清除代码放在函数的末尾来简化其代码,该函数的所有“退出点”都将进入清除标签。这样,您不必在函数的每个“退出点”都编写清理代码。

退出嵌套循环

如果您处于嵌套循环中并且需要脱离所有循环,那么goto可以比break语句和if-checks更加简洁明了。

低级性能改进

这仅在性能至关重要的代码中有效,但是goto语句执行得非常快,并且在遍历函数时可以助您一臂之力。但是,这是一把双刃剑,因为编译器通常无法优化包含gotos的代码。

我认为,就像在许多其他情况下一样,在所有这些情况下,的用法都goto被用作摆脱自己编写的代码的手段,并且通常是可重构代码的症状。


28
第一个问题可以通过finally现代语言中的块很好地解决,第二个问题可以通过标记为breaks 来解决。但是,如果您坚持使用C,goto那么这几乎是优雅解决这些问题的唯一方法。
Chinmay Kanchi 2010年

2
非常好点。我喜欢Java如何允许突破多个级别的循环
Casebash 2010年

4
@Chinmay-最终块仅适用于1)具有它们的现代语言,并且b)您可以忍受开销(异常处理的确有开销。)也就是说,using finally仅在那些条件下有效。在非常罕见但有效的情况下,goto是必经之路。
luis.espinal,2010年

21
好的,人们…… break 3or break myLabel和之间的区别到底是什么goto myLabel。哦,就是关键字。如果您使用的breakcontinue或一些类似的关键字,您是INFACT使用goto(如果您正在使用for, while, foreach, do/loop您使用的是有条件的转到)
马修粉饰

4
关于低级性能-现代处理器的行为可能难以预测(流水线,无序执行),因此在99%的情况下可能不值得,甚至可能更慢。@MatthewWhited:作用域。如果在任意点输入范围,(对人类而言)尚不清楚应调用什么构造函数(问题同样存在于C中)。
Maciej Piechotka'2

42

更高级别的控制流构造倾向于对应于问题域中的概念。if / else是基于某种条件的决策。循环表示重复执行某些操作。甚至有一个break语句也说:“我们一直在重复这样做,但是现在我们需要停止”。

另一方面,goto语句倾向于对应于正在运行的程序中的概念,而不是问题域中的概念。它说,在指定点继续执行该计划。读取代码的人必须推断出对于问题域的含义。

当然,所有更高级别的构造都可以用gotos和简单的条件分支来定义。这并不意味着它们只是变相的东西。将它们视为受限的对象 -正是这些限制使它们变得有用。break语句的实现是跳转到封闭循环的末尾,但最好将其视为对整个循环的操作。

在所有其他条件相同的情况下,其结构反映问题域结构的代码往往更易于阅读和维护。

在任何情况下,绝对没有必要使用goto语句(有一个这样的定理),但在某些情况下,它可能是最不坏的解决方案。这些情况因语言而异,具体取决于语言支持的高层构造。

例如,在C语言中,我认为有三种适合使用goto的基本方案。

  1. 突破嵌套循环。如果该语言具有标记的break语句,则这是不必要的。
  2. 在发生错误或其他意外事件的情况下,摆脱一段代码(通常是一个函数体)。如果语言有例外,这将是不必要的。
  3. 实现一个显式的有限状态机。在这种情况下(并且我认为仅在这种情况下),goto直接对应于问题域中的概念,从一个状态转换为指定的另一种状态,其中当前状态由当前正在执行的代码块表示。

另一方面,还可以在循环内部使用switch语句来实现显式有限状态机。这样做的好处是,每个状态都在代码中的同一位置开始,例如,这对于调试很有用。

goto在一种相当现代的语言(支持if / else和loops的语言)中的主要用途是模拟该语言所缺少的控制流构造。


5
到目前为止,这实际上是最好的答案-令人惊讶的是,这个问题已经存在一年半了。:-)
康拉德·鲁道夫2012年

1
+1表示“迄今为止最好的答案”。---“在一种相当现代的语言中,goto的主要用途(一种支持if / else和loops的语言)是模拟该语言所缺少的控制流构造。”
戴夫·多普森

在开关状态机中,每次写入控制值实际上都是一个goto。SSSM通常是可以使用的良好结构,因为它们将SM状态与执行结构分离开来,但是它们并没有真正消除“困境”。
supercat

2
“在所有其他条件相同的情况下,其结构反映问题域结构的代码往往更易于阅读和维护。” 我已经很长时间了解了这一点,但缺乏用其他程序员可以并且将真正理解的方式如此精确地传达它的语言。这次真是万分感谢。
2016年

“结构化程序定理”使我感到很有趣,因为它倾向于清楚地表明使用结构化编程语句模拟goto的可读性远不仅仅使用goto!

11

当然,这取决于编程语言。goto引起争议的主要原因是,当编译器让您过于自由地使用它时,会产生不良影响。例如,如果它使您goto以现在可以访问未初始化变量的方式使用,或者更糟糕的是,跳入另一个方法并弄乱了调用堆栈,则可能会出现问题。禁止不必要的控制流是编译器的责任。

Java试图通过goto完全禁止来“解决”此问题。但是,Java允许您returnfinally块内部使用,从而导致意外吞下异常。仍然存在相同的问题:编译器没有完成其工作。goto从语言中删除并不能解决问题。

在C#中,goto是安全的,因为breakcontinuetry/catch/finallyreturn。它不允许您使用未初始化的变量,不允许您跳出finally块等。编译器会抱怨。这是因为它解决了实际的问题,就像我所说的无意义的控制流程。goto不会神奇地取消定值分析和其他合理的编译器检查。


您的意思是C#goto不是邪恶的吗?如果是这样的话,那么每一种日常使用的现代语言都具有goto结构,不仅是C#...
lvella

9

是。当您的循环嵌套在多个级别的深处时,这goto是优雅地突破内部循环的唯一方法。另一个选择是设置一个标志,如果该标志满足条件,则退出每个循环。这确实很丑陋,而且容易出错。在这些情况下,goto就是更好。

当然,Java的带标签的break语句执行相同的操作,但是不允许您跳到代码中的任意点,这可以巧妙地解决问题,而又不会使事情变得goto邪恶。


有人愿意解释下降投票吗?这是对这个问题的完全有效的答案……
Chinmay Kanchi 2010年

4
goto绝不是一个完美的答案。当循环嵌套在多个层次的深处时,您的问题是您无法架构代码来避免多嵌套循环。程序调用不是敌人。(请阅读“ Lambda:The Ultimate ...”论文。)使用goto打破嵌套在多个层次上的循环将创可贴放在开放的多处骨折上:这可能很简单,但并不正确回答。
约翰·斯特罗姆

6
当然,当需要深度嵌套的循环时,永远不会有奇怪的情况吗?成为一名优秀的程序员不仅要遵守规则,而且还要知道何时打破规则。
Chinmay Kanchi 2011年

2
@ JohnR.Strohm永不言败。如果在编程中使用诸如“从不”之类的术语,则显然没有处理过极端情况,优化,资源约束等。在深度嵌套的循环中,goto实际上确实是退出循环的最干净,最优雅的方式。重构代码的数量无关紧要。
Sujay Phadke

@ JohnR.Strohm:从VB.NET切换到C#时,第二大最缺少的功能:Do循环。为什么?因为我可以将需要深入分解的一个循环更改为一个Do循环并输入Exit Do,所以没有GoTo。嵌套循环一直在发生。打破堆栈的发生率大约为%。
约书亚

7

大部分的挫折来自某种形式的“宗教”,它源于Djikstra神,该神在60年代初就因其不分青红皂白的力量而引人注目:

  • 跳到任何代码块
    • 从一开始就没有执行功能
    • 从一开始就不执行循环
    • 跳过变量初始化
  • 无需任何可能的清理就可以跳出任何代码块。

这与goto现代语言的声明无关,后者的存在仅是由于支持了所提供语言之外的代码结构的创建。

特别是上面的第一个主要点已被允许,第二个主要点已被清除(如果您goto超出了一个块,则堆栈将正确展开,并调用所有适当的析构函数)

您可以参考此答案,以了解即使不使用goto的代码也难以理解的想法。问题不在于goto本身,而是对它的不良使用。

我可以不使用if而只编写整个程序for。当然,它的可读性不佳,外观笨拙且不必要地复杂。

但是问题不是for。是我。

之类的东西breakcontinuethrowbool needed=true; while(needed) {...},等被提超过伪装 goto从Djikstrarian狂热者的弯刀逃逸而去,现代laguages-的发明- 50年后,仍然希望自己的囚犯。他们忘了Djikstra在说什么,他们只记得笔记的标题(GOTO认为这是有害的,甚至连他的头衔都没有:它被编辑器更改了),并且责备bash,bash并责怪每一个拥有这4个内容的结构字母顺序放置。

现在是2011年:是时候该理解不理会Djikstra令人信服gotoGOTO声明了。


1
“诸如中断,继续,掷球,布尔值之类的东西= true;((需要){...}等)不仅仅要伪装成goto,”我不同意这一点。我更喜欢“诸如break等东西受限于 goto”之类的东西。goto的主要问题在于它通常过于强大。在某种程度上,当前的goto功能不如Dijkstra强大,您的回答对我来说很好。
Muhammad Alkarouri'2

2
@MuhammadAlkarouri:我完全同意。您刚刚找到了一个更好的措辞来准确表达我的概念。我的观点是,这些限制有时可能不适用,并且您所需要的限制种类也不在语言中。然后,可以通过更轻松,更专业的方式解决问题。例如,Java break n在C ++中不可用,因此goto escape,即使escape不需要从n-ple循环中出来也是如此。
Emilio Garavaglia'2

4

只要在函数中是本地的,这里或那里的奇异goto几乎不会严重损害可读性。它通常会通过吸引人们注意以下事实而受益:该代码中发生了一些不寻常的事情,需要使用某种不常见的控制结构。

如果(local)gotos严重损害了可读性,则通常表明包含goto的函数过于复杂。

我放入一段C代码中的最后一个步骤是构建一对互锁的循环。它不符合“可接受的” goto使用的常规定义,但是最终该功能变得更小,更清晰。为了避免goto,需要特别混乱地违反DRY。


1
古老的“ Lay's Potato Chips”口号最能说明问题的答案:“下注不能只吃一个”。在您的示例中,您有两个“互锁”(无论是什么意思,我敢打赌这不是很漂亮)循环,而goto给您带来了便宜。维护人员呢?在您被公交车撞到后,谁必须修改该代码?goto会让他的生活更轻松或更艰难吗?这两个循环是否需要重新考虑?
John R. Strohm

3

我认为整个问题一直是树桩错误的情况。

这样的GOTO在我看来似乎没有问题,但它通常是一种实际罪过的症状:意大利面条式代码。

如果GOTO导致流量控制线严重交叉,那就不好了。如果没有穿过流量控制线,则无害。在介于两者之间的灰色区域中,我们有循环救助之类的东西,仍然有些语言没有添加涵盖所有合法灰色情况的构造。

我发现自己多年来实际使用过的唯一情况是循环的情况,决策点位于循环的中间。您将剩下重复的代码,标志或GOTO。我发现GOTO解决方案是三者中最好的。这里没有流量控制线的交叉,这是无害的。


您提到的情况有时被称为“半循环”或“ N加半循环”,这是gotos跳入循环的一个著名用例(如果语言允许,则为另一种情况,请跳至不在中间的循环,通常不带goto)是微不足道的。
Konrad Rudolph

(可惜,大多数语言都禁止跳入循环中。)
Konrad Rudolph'2

@KonradRudolph:如果使用GOTO实现“循环”,则没有其他循环结构可以进入。
罗伦·佩希特尔

1
我认为goto高呼控制流不适合正常的结构化编程模式。因为适合结构化编程模式的控制流比不适合结构化编程模式的控制流更容易推理,因此应尽可能尝试使用这种模式。如果代码确实适合这种模式,则不应大声疾呼它不适合。另一方面,在代码确实不适合此类模式的情况下,大喊大叫可能比假装代码不适合时更好。
超级猫

1

是的,可以使用goto来丰富开发人员的经验:http : //adamjonrichardson.com/2012/02/06/long-live-the-goto-statement/

但是,就像使用任何强大的工具(指针,多重继承等)一样,必须使用该工具进行训练。链接中提供的示例使用PHP,它将goto构造的用法限制为相同的函数/方法,并且禁用了跳转到新的控制块(例如,循环,switch语句等)的功能。


1

取决于语言。例如,它仍广泛用于Cobol编程中。我还在Barionet 50设备上工作,该设备的固件编程语言是早期的BASIC方言,当然,您需要使用Goto。


0

我会说不。如果您发现需要使用GOTO,我敢打赌,需要重新设计代码。


3
也许可以,但是您没有提供任何理由
Casebash 2010年

迪克斯特拉(Dijkstra)于几十年前提供了详细的推论。
John R. Strohm

2
只是教条。没有共鸣。注意:Djikstra并非有效的代言人:它指的是60年代初期的BASIC或FORTRAN GOTO陈述。他们与今天真正的贫血(与那些人的力量相比)无关。
Emilio Garavaglia

0

goto在将旧版汇编代码移植到C时可能会很有用。首先,将C指令逐条转换(goto代替汇编branch指令)可以实现非常快速的移植。


-1

我会说不。应该始终使用更特定于goto所要解决问题的工具来代替它们(除非您的语言不可用)。例如,break语句和异常解决了先前goto解决的循环转义和错误处理问题。


我认为,当语言同时具有两种功能时,goto这些语言通常是不存在的。
Chinmay Kanchi 2010年

但这是因为他们不需要它们,还是因为人们认为他们不需要它们?
Michael K

您的观点是偏执的。在许多情况下,当goto最接近问题域语义的东西出现时,其他任何事情都是肮脏的。
SK-logic

@ SK-logic:我认为“ bigoted”一词并不意味着您认为的含义。
基思·汤普森

1
@Keith,“全神贯注于他或她自己的见解和偏见”,这就是我一直在这里观察到的内容,这是一副扑朔迷离的蒙住眼睛的东西。狂热者至少说过“应该永远被替换”。没有什么比一个正确,正确的观点更重要的了,而仅仅是纯粹的偏执。这种“总是”不会留下任何意见的余地,看到了吗?
SK-logic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.