每个人都知道迪杰斯特拉 (Dijkstra) 给编辑的信:转到被认为有害的声明 (还 这里 .html 成绩单和 这里 .pdf),从那时起,人们就强烈要求尽可能避免使用 goto 语句。虽然可以使用 goto 生成不可维护的、庞大的代码,但它仍然存在 现代编程语言. 。即使是高级的 延续 Scheme 中的控制结构可以描述为一个复杂的 goto。

什么情况下需要使用 goto?什么时候最好避免?

作为后续问题:C 提供了一对函数 setjmp 和 longjmp,它们不仅能够在当前堆栈帧内进行跳转,而且还能够在任何调用帧内进行跳转。这些是否应该被视为与 goto 一样危险?更危险吗?


迪杰斯特拉本人对这个头衔感到遗憾,因为他对此不负有责任。在......的最后 EWD1308 (还 这里 .pdf)他写道:

最后写一个小故事作为记录。1968年,ACM的通讯以标题为“我的文本”。该声明认为有害“在后来的几年中,最常见的是,令人遗憾的是,经常被看不过它的标题更多的作者,这成为我名声的基石,成为了我的名声:我们会看到几乎所有X的标题为“ X被认为有害”的各种文章,其中包括一篇名为“ Dijkstra被认为有害”的文章。但是发生了什么?我已经提交了标题的论文”反对goto声明的案件“为了加快其出版物的速度,编辑已经改为“给编辑的信”,在此过程中,他给了它自己发明的新标题!编辑是Niklaus Wirth。

关于这个主题的一篇经过深思熟虑的经典论文,与 Dijkstra 的论文相匹配,是 使用 go to 语句进行结构化编程, ,唐纳德·E.高德纳.阅读两者都有助于重新建立背景和对主题的非教条理解。在这篇论文中,Dijkstra对此案的观点进行了报道,并且更加强烈:

唐纳德·E。高德纳: 我相信,通过提出这样的观点,我实际上并没有彻底不同意Dijkstra的想法,因为他最近写了以下内容:“请不要陷入相信我对[to to to Statement]的陷阱。 我有一种不舒服的感觉,那就是别人正在从中造成一种宗教信仰,好像可以通过一种简单的编码纪律来解决编程的概念问题。"

有帮助吗?

解决方案

以下陈述是概括性的;虽然总是可以提出例外请求,但通常(以我的经验和拙见)不值得冒险。

  1. 不受限制地使用内存地址(GOTO 或原始指针)提供了太多犯容易避免的错误的机会。
  2. 到达代码中特定“位置”的方法越多,人们对此时系统状态的信心就越低。(见下文。)
  3. 恕我直言,结构化编程不是“避免 GOTO”,而是更多地使代码结构与数据结构相匹配。例如,重复的数据结构(例如数组、顺序文件等)自然是由重复的代码单元处理的。具有内置结构(例如while、for、until、for-each 等)允许程序员避免重复相同陈词滥调的代码模式的乏味。
  4. 即使 GOTO 是低级实现细节(并非总是如此!),它也低于程序员应该考虑的级别。有多少程序员用原始二进制来平衡他们的个人支票簿?有多少程序员担心磁盘上的哪个扇区包含特定记录,而不是仅仅为数据库引擎提供密钥(如果我们真的根据物理磁盘扇区编写所有程序,有多少种情况可能会出错)?

上述脚注:

关于第2点,请考虑以下代码:

a = b + 1
/* do something with a */

在代码中的“做某事”点,我们可以高度自信地声明 a 大于 b. 。(是的,我忽略了未捕获整数溢出的可能性。我们不要陷入一个简单的例子。)

另一方面,如果代码是这样读的:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

获得标签 10 的方法多种多样,这意味着我们必须更加努力地工作,才能对之间的关​​系充满信心。 ab 在那时候。(事实上​​,一般情况下是无法判定的!)

关于第 4 点,代码中“去某个地方”的整个概念只是一个隐喻。除了电子和光子(用于废热)之外,没有任何东西真正“移动”到 CPU 内部的任何地方。有时我们会放弃另一种更有用的隐喻。我记得(几十年前!)遇到过一种语言

if (some condition) {
  action-1
} else {
  action-2
}

通过将action-1和action-2编译为外线无参数例程,然后使用单个双参数VM操作码(该操作码使用条件的布尔值来调用其中之一)在虚拟机上实现。这个概念只是“选择现在调用什么”而不是“去这里或去那里”。再说一遍,只是换个比喻。

其他提示

XKCD's GOTO Comic

我的一位同事说,使用 GOTO 的唯一原因是,如果您将自己编程到了一个角落,那么这是唯一的出路。换句话说,提前正确设计,以后就不需要使用 GOTO。

我认为这部漫画说明了“我可以重组程序的流程,也可以使用一个小'goto'。”当您的设计薄弱时,Goto是一个薄弱的出路。 迅猛龙捕食弱者.

有时,在单个函数中使用 GOTO 作为异常处理的替代方法是有效的:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

COM 代码似乎经常陷入这种模式。

我只记得使用过一次 goto。我有一系列五个嵌套计数循环,我需要能够根据某些条件尽早从内部突破整个结构:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

我可以轻松地声明一个布尔中断变量并将其用作每个循环的条件的一部分,但在这种情况下,我认为 GOTO 同样实用且可读。

没有迅猛龙攻击我。

我们已经有了这个 讨论 我站在一旁 我的观点.

此外,我厌倦了人们将高级语言结构描述为“goto 变相”因为他们显然没有明白这一点 根本不. 。例如:

甚至Scheme中先进的延续控制结构也可以被描述为一个复杂的goto。

这完全是无稽之谈。 每一个 控制结构可以按照以下方式实现 goto 但这种观察完全是微不足道且毫无用处的。 goto 不被认为是有害的,因为它的积极影响,而是因为它的负面影响,而这些已经被结构化编程消除了。

同样,说“GOTO 是一个工具,并且与所有工具一样,它可以被使用和滥用”也是完全没有道理的。没有现代建筑工人会使用岩石,并声称它是“是一种工具”。岩石已被锤子所取代。 goto 已被控制结构所取代。如果建筑工人被困在野外,没有锤子,他当然会用石头代替。如果程序员必须使用不具备 X 功能的劣质编程语言,那么她当然可能不得不使用 goto 反而。但是,如果她在其他地方使用它而不是适当的语言功能,那么她显然没有正确理解该语言并且错误地使用了它。真的就是这么简单。

在我的程序清单中,Goto 的位置非常靠后。这并不意味着这是不可接受的。

Goto 对于状态机来说非常有用。循环中的 switch 语句是(按照典型重要性的顺序):(a) 实际上并不代表控制流,(b) 丑陋,(c) 可能效率低下,具体取决于语言和编译器。因此,您最终每个状态写一个功能,并做诸如“ return next_state”之类的事情;甚至看起来像Goto。

诚然,以易于理解的方式对状态机进行编码是很困难的。然而,这些困难都与使用 goto 无关,并且不能通过使用替代控制结构来减少这些困难。除非您的语言具有“状态机”结构。我的没有。

在极少数情况下,当您的算法确实最容易理解时,通过一系列节点(状态)的路径通过一组有限的允许转换(goto)连接,而不是通过任何更具体的控制流(循环、条件等) ),那么这应该在代码中明确表示。你应该画一个漂亮的图表。

setjmp/longjmp 非常适合实现异常或类似异常的行为。虽然没有受到普遍赞扬,但异常通常被认为是“有效”的控制结构。

setjmp/longjmp 比 goto “更危险”,因为它们更难正确使用,更不用说理解性了。

从来没有,也不会有任何语言,其中很难编写错误的代码。——唐纳德·高德纳。

从 C 语言中去掉 goto 并不会让用 C 语言编写好的代码变得更容易。事实上,它宁愿忽略 C 的这一点 应该 能够充当一种美化的汇编语言。

接下来是“被认为有害的指针”,然后是“被认为有害的鸭子类型”。那么当他们来夺走你不安全的编程结构时,谁会来保护你呢?呃?

Linux:在内核代码中使用 goto 在 Kernel Trap 上,与 Linus Torvalds 和一个“新人”讨论了在 Linux 代码中使用 GOTO。其中有一些非常好的观点,Linus 也表现出了一贯的傲慢:)

一些段落:

莱纳斯:“不,您被CS人洗脑了,他们认为尼克劳斯·沃思实际上知道他在说什么。他没有。他没有一个脆弱的线索。”

-

莱纳斯:“我认为Goto很好,而且它们通常比大量的凹痕更可读。”

-

莱纳斯:“当然,用像帕斯卡(Pascal)这样的愚蠢语言,标签无法描述性,goto的语言可能很糟糕。”

在C中, goto 只在当前函数的范围内工作,这往往会本地化任何潜在的错误。 setjmplongjmp 危险得多,因为它们是非本地的、复杂的并且依赖于实施。然而在实践中,它们太晦涩和不常见,不会引起很多问题。

我相信危险 goto 在C中是非常夸张的。记住原来的 goto 争论发生在老式 BASIC 等语言时代,初学者会编写像这样的意大利面条代码:

3420 IF A > 2 THEN GOTO 1430

这里 Linus 描述了一个适当的使用 goto: http://www.kernel.org/doc/Documentation/CodingStyle (第7章)。

今天,很难看出有什么大不了的 GOTO 声明,因为“结构化编程”人们大多赢得了辩论,并且当今的语言有足够的控制流结构来避免 GOTO.

数一下数量 goto现代 C 程序中。现在添加数量 break, continue, , 和 return 声明。此外,添加您使用的次数 if, else, while, switch 或者 case. 。大约有多少 GOTO如果您在 1968 年 Dijkstra 写信时使用 FORTRAN 或 BASIC 编写程序,那么您的程序就会有这样的情况。

当时的编程语言缺乏控制流。例如,在最初的达特茅斯 BASIC 中:

  • IF 声明没有 ELSE. 。如果你想要一个,你必须写:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • 即使你的 IF 声明不需要 ELSE, ,它仍然限于单行,通常由 GOTO.

  • 没有 DO...LOOP 陈述。对于非FOR 循环,你必须用显式的方式结束循环 GOTO 或者 IF...GOTO 回到最初。

  • 没有 SELECT CASE. 。你必须使用 ON...GOTO.

所以,你最终得到了 很多GOTO在你的程序中。你不能依赖于以下限制 GOTOs 到单个子例程内(因为 GOSUB...RETURN 是一个很弱的子例程概念),所以这些 GOTO可以走了 任何地方. 。显然,这使得控制流程难以遵循。

这就是反的地方GOTO 动静来自。

在某些情况下,Go To 可以提供一种“真正的”异常处理的替代方法。考虑:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

显然,此代码已被简化以占用更少的空间,因此不要太在意细节。但考虑一下我已经见过太多次的另一种选择 生产 为了避免使用 goto,编码人员编写了荒谬的代码:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

现在,从功能上讲,这段代码做了完全相同的事情。事实上,编译器生成的代码几乎是相同的。然而,在程序员的热心安抚中 野后 (可怕的学术斥责之神),这个程序员完全打破了潜在的成语: while 循环表示,并且对代码的可读性做了实数。 这并不更好。

所以,这个故事的寓意是,如果你发现自己为了避免使用 goto 而采取了一些非常愚蠢的做法,那就不要这样做。

唐纳德·E。Knuth 在《LiterateProgramming》一书中回答了这个问题,1992 年 CSLI。上页。17 有一篇文章“使用 goto 语句进行结构化编程“(PDF)。我认为这篇文章也可能发表在其他书中。

文章描述了 Dijkstra 的建议并描述了该建议有效的情况。但他还给出了许多反例(问题和算法),仅使用结构化循环无法轻松重现这些反例。

这篇文章包含了问题的完整描述、历史、例子和反例。

被 Jay Ballou 添加答案所吸引,我将添加 0.02 英镑。如果 Bruno Ranschaert 还没有这样做,我会提到 Knuth 的“使用 GOTO 语句进行结构化编程”文章。

我没有看到讨论的一件事是 Fortran 教科书上教授的那种代码,虽然并不常见,但却是这样的。例如 DO 循环的扩展范围和开放编码的子例程(请记住,这将是 Fortran II、Fortran IV 或 Fortran 66 - 而不是 Fortran 77 或 90)。语法细节至少有可能不准确,但概念应该足够准确。每种情况下的片段都位于单个函数内。

请注意,这本优秀但过时(且已绝版)的书'编程风格的要素,第二版Kernighan & Plauger 所著的一本书包含了当时(70 年代末)编程教科书上滥用 GOTO 的一些现实例子。然而,下面的材料并非来自该书。

DO 循环的扩展范围

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

造成这种废话的原因之一是老式的打孔卡。您可能会注意到,标签(完全不按顺序排列,因为这是规范风格!)位于第 1 列(实际上,它们必须位于第 1-5 列),代码位于第 7-72 列(第 6 列是延续)标记列)。第 73-80 列将被赋予一个序列号,并且有机器可以将打孔卡组按序列号顺序进行排序。如果您的程序位于已排序的卡片上,并且需要在循环中间添加一些卡片(行),则必须在这些额外的行之后重新打孔所有内容。但是,如果您用 GOTO 内容替换一张卡片,则可以避免对所有卡片重新排序 - 您只需将新卡片用新的序列号塞到例程末尾即可。可以将其视为“绿色计算”的首次尝试 - 节省打孔卡(或者更具体地说,节省重新打字的劳动力 - 以及节省随之而来的重新键入错误)。

哦,你可能还注意到我在作弊,而不是大喊大叫——Fortran IV 通常都是用大写字母编写的。

开放编码子程序

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

标签 76 和 54 之间的 GOTO 是计算 goto 的一个版本。如果变量 i 的值为 1,则转到列表中的第一个标签 (123);如果它的值为 2,则转到第二个,依此类推。从 76 到计算出的 goto 的片段是开放编码的子例程。它是一段类似于子例程的代码,但写在函数体中。(Fortran 也有语句函数 - 它们是适合单行的嵌入式函数。)

还有比计算 goto 更糟糕的构造 - 您可以为变量分配标签,然后使用指定的 goto。谷歌搜索 指定的转到 告诉我它已从 Fortran 95 中删除。结构化编程革命可以说是从 Dijkstra 的“GOTO 被认为有害”的信件或文章开始的。

如果不了解 Fortran 中所做的事情(以及其他语言中的事情,其中​​大多数已经被抛在一边),我们这些新手很难理解 Dijkstra 正在处理的问题的范围。哎呀,直到那封信发表十年后我才开始编程(但我确实不幸使用 Fortran IV 编程了一段时间)。

后藤认为很有帮助。

我从 1975 年开始编程。对于 20 世纪 70 年代的程序员来说,“goto 被认为是有害的”这句话或多或少地表明,具有现代控制结构的新编程语言值得尝试。我们确实尝试了新语言。我们很快就转变了。我们再也没有回去过。

我们再也没有回去过,但是,如果你还年轻,那么你一开始就从来没有去过那里。

现在,古代编程语言的背景可能没有多大用处,除非可以作为程序员年龄的指标。尽管如此,年轻的程序员缺乏这个背景,所以他们不再理解“goto 被认为有害”这个口号所传达的信息。 在推出时向其目标受众展示。

一个人不懂的口号是没有多大启发性的。也许最好忘记这些口号。这样的口号没有帮助。

然而,“后藤被认为是有害的”这个特殊的口号已经拥有了它自己的不死生命。

goto能不被滥用吗?回答:当然,但那又怎样呢?几乎每个编程元素 被虐待。谦卑者 bool 例如,虐待的频率比我们中一些人愿意相信的要多。

相比之下,自 1990 年以来,我不记得遇到过任何实际的 goto 滥用案例。

goto 最大的问题可能不是技术问题,而是社会问题。不太了解的程序员有时似乎觉得弃用 goto 让他们听起来很聪明。您可能有时必须满足这样的程序员。这就是人生。

今天goto最糟糕的地方是它没有被充分利用。

不存在这样的事情 GOTO 被认为是有害的.

GOTO 是一个工具,和所有工具一样,它可以被使用和 被虐待.

然而,编程世界中的许多工具都倾向于被 被虐待 不仅仅是存在 用过的, ,GOTO 就是其中之一。这 Delphi的声明是另一回事。

我个人也不使用 在典型的代码中, ,但我对两者都有过奇怪的用法 这是有道理的,替代解决方案将包含更多代码。

最好的解决方案是编译器只是警告您关键字是 被污染的, ,并且您必须在语句周围填充一些编译指示以消除警告。

这就像告诉你的孩子 不可以用剪刀奔跑. 。剪刀并不坏,但使用它们可能并不是保持健康的最佳方法。

自从我开始在 Linux 内核中做一些事情以来,goto 不再像以前那样困扰我了。起初,我有点震惊地看到他们(内核人员)在我的代码中添加了 goto。从那以后,我已经习惯了在某些有限的上下文中使用 goto,现在我自己也会偶尔使用它们。通常,它是跳转到函数末尾以执行某种清理和救助的 goto,而不是在函数中的多个位置重复相同的清理和救助。通常,它不是大到足以移交给另一个功能的东西 - 例如释放一些本地 (k)malloc 的变量是一个典型的情况。

我编写的代码只使用了一次 setjmp/longjmp 。它位于 MIDI 鼓音序器程序中。播放发生在与所有用户交互无关的单独进程中,并且播放进程使用与 UI 进程共享的内存来获取播放所需的有限信息。当用户想要停止播放时,播放过程只是执行一个 longjmp“回到开头”来重新开始,而不是在用户希望停止时对它恰好正在执行的位置进行一些复杂的展开。它工作得很好,很简单,在那种情况下我从未遇到过任何与之相关的问题或错误。

setjmp/longjmp 有它们的位置——但那个地方你不太可能访问,但很长一段时间才会访问一次。

编辑:我只是看了代码。我实际上使用的是 siglongjmp(),而不是 longjmp(并不是说这有什么大不了的,但我忘记了 siglongjmp 甚至存在。)

只要你能够独立思考,就永远不会。

如果您用 C 语言编写 VM,则使用(gcc 的)计算 goto 如下所示:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

比循环内的传统开关工作得快得多。

因为 goto 可用于令人困惑的元编程

Goto 既是一个 高水平 和一个 低级 控制表达式,因此它没有适合大多数问题的适当设计模式。

它是 低级 从某种意义上说,goto 是一种原始操作,它实现了更高层次的操作,例如 while 或者 foreach 或者其他的东西。

它是 高水平 从某种意义上说,当以某些方式使用时,它需要以清晰的顺序、不间断的方式执行代码(结构化循环除外),并将其更改为逻辑片段,这些逻辑片段具有足够的 gotos,动态重组的逻辑的抓包。

所以,有一个 平淡的邪恶的 侧面 goto.

平淡的一面 向上的 goto 可以实现完全合理的循环,而向下的 goto 可以实现完全合理的循环 break 或者 return. 。当然,实际的 while, break, , 或者 return 会更具可读性,因为可怜的人不必模拟效果 goto 为了了解大局。所以,总的来说,这是一个坏主意。

邪恶的一面 涉及一个例程,不将 goto 用于 while、break 或 return,而是将其用于调用的内容 意大利面条逻辑. 。在这种情况下,喜欢 goto 的开发人员正在从 goto 的迷宫中构建代码片段,理解它的唯一方法是在心理上模拟它作为一个整体,当有很多 goto 时,这是一项非常累人的任务。我的意思是,想象一下评估代码的麻烦 else 并不完全是倒数 if, ,其中嵌套 ifs可能允许一些被外部拒绝的东西 if, 等等等等

最后,为了真正涵盖这个主题,我们应该注意到,除了 Algol 之外,基本上所有早期语言最初都只做出了受其版本约束的单个语句。 if-then-else. 。所以,执行条件块的唯一方法是 goto 使用反条件条件围绕它。我知道这很疯狂,但我读过一些旧规格。请记住,第一台计算机是用二进制机器代码编程的,因此我认为任何类型的 HLL 都是救星;我想他们对于自己获得的 HLL 功能并不太挑剔。

说了这么多,我曾经粘过一个 goto 进入我编写的每个程序 “只是为了惹恼纯粹主义者”.

拒绝程序员使用 GOTO 语句就像告诉木匠不要使用锤子,因为当他敲钉子时可能会损坏墙壁。真正的程序员知道如何以及何时使用 GOTO。我跟踪了一些所谓的“结构化程序”,我看到了如此可怕的代码,只是为了避免使用 GOTO,这样我就可以射击程序员。好吧,为了替对方辩护,我也见过一些真正的意大利面条代码,那些程序员也应该被枪毙。

这只是我找到的一个小代码示例。

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

- - - - - - - - - - - -或者 - - - - - - - - - - -

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

“在这个链接中 http://kerneltrap.org/node/553/2131"

讽刺的是,消除 goto 引入了一个错误:自旋锁调用被省略。

原始论文应该被认为是“无条件 GOTO 被认为是有害的”。它特别提倡一种基于条件的编程形式(if) 和迭代 (while) 构造,而不是早期代码常见的测试和跳转。 goto 在某些语言或不存在适当控制结构的情况下仍然有用。

关于我唯一同意的地方Goto 可以 当需要处理错误时使用,并且每个特定点发生错误都需要特殊处理。

例如,如果您正在获取资源并使用信号量或互斥体,则必须按顺序获取它们,并且应该始终以相反的方式释放它们。

有些代码需要一种非常奇怪的模式来获取这些资源,并且您不能只编写一个易于维护和理解的控制结构来正确处理这些资源的获取和释放以避免死锁。

不使用 goto 总是可以正确完成任务,但在这种情况下以及其他一些情况下,Goto 实际上是更好的解决方案,主要是为了可读性和可维护性。

-亚当

现代 GOTO 的一种用法是由 C# 编译器为由收益返回定义的枚举器创建状态机。

GOTO 应该由编译器而不是程序员使用。

在 C 和 C++(以及其他罪魁祸首)标记出中断和继续之前,goto 将继续发挥作用。

如果 GOTO 本身是邪恶的,那么编译器也会是邪恶的,因为它们生成 JMP。如果跳转到代码块(尤其是跟随指针)本质上是邪恶的,那么 RETurn 指令也将是邪恶的。相反,邪恶在于滥用的可能性。

有时,我不得不编写必须跟踪多个对象的应用程序,其中每个对象必须遵循复杂的状态序列来响应事件,但整个事情绝对是单线程的。如果用伪代码表示,典型的状态序列将是:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

我确信这并不新鲜,但我在 C(++) 中处理它的方式是定义一些宏:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

然后(假设状态最初为0)上面的结构化状态机变成结构化代码:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

通过这种变化,可以有 CALL 和 RETURN,因此某些状态机可以像其他状态机的子例程一样运行。

这很不寻常吗?是的。维护者需要学习一些知识吗?是的。这种学习有回报吗?我想是这样。如果没有跳转到块的 GOTO 是否可以完成?没有。

我避免使用它,因为同事/经理无疑会在代码审查中或偶然发现它时质疑它的使用。虽然我认为它有用途(例如错误处理情况),但您会与其他遇到某种问题的开发人员发生冲突。

这不值得。

实际上,我发现自己被迫使用 goto,因为我确实想不出更好(更快)的方法来编写此代码:

我有一个复杂的对象,我需要对其进行一些操作。如果对象处于一种状态,那么我可以执行快速版本的操作,否则我必须执行慢速版本的操作。问题是,在某些情况下,在慢速操作的中间,有可能意识到这可以通过快速操作来完成。

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

这是实时 UI 代码中速度关键的一段,所以我真的认为 GOTO 在这里是合理的。

雨果

几乎在所有可以使用 goto 的情况下,您都可以使用其他构造来执行相同的操作。无论如何,编译器都会使用 Goto。

我个人从不明确使用它,也不需要。

我没见过的一件事 任何 这里的答案是“goto”解决方案通常是 更高效 比经常提到的结构化编程解决方案之一更重要。

考虑许多嵌套循环的情况,其中使用“goto”而不是一堆 if(breakVariable) 部分显然效率更高。“将循环放入函数并使用 return”的解决方案通常是完全不合理的。在循环使用局部变量的情况下,您现在必须通过函数参数将它们全部传递,可能会处理由此产生的大量额外麻烦。

现在考虑清理案例,我自己经常使用它,而且很常见,可能是造成许多语言中不可用的 try{} catch {} 结构的原因。完成同一件事所需的检查和额外变量的数量远比进行跳转的一两条指令要差得多,而且,附加函数解决方案根本不是解决方案。你不能告诉我这更易于管理或更具可读性。

现在,在许多情况下,对于许多程序员来说,代码空间、堆栈使用和执行时间可能并不重要,但是当您处于只有 2KB 代码空间可供使用的嵌入式环境中时,需要 50 字节的额外指令来避免明确定义的指令“goto”只是可笑的,而且这种情况并不像许多高级程序员认为的那么罕见。

“goto 是有害的”这一说法对于转向结构化编程非常有帮助,即使它总是过于概括。至此,我们都已经听过它了,因此对使用它持谨慎态度(我们应该如此)。当它显然是适合工作的工具时,我们不需要害怕它。

您可以使用它来打破深度嵌套循环,但大多数时候您的代码可以重构为更干净,而无需深度嵌套循环。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top