每个人都知道Dijkstra的Letters to the editor: go to statement considered harmful(也是here .html成绩单和here .pdf)并且从那时起就有一个强大的推动力,只要有可能就避开goto语句。虽然可以使用goto来生成不可维护的蔓延代码,但它仍然保留在modern programming languages中。甚至Scheme中的高级continuation控制结构也可以被描述为复杂的goto。
什么情况需要使用goto?什么时候最好避免?
作为后续问题:C提供了一对函数setjmp和longjmp,它们不仅可以在当前堆栈帧内,而且可以在任何调用帧中进行转换。这些应该被视为像goto一样危险吗?更危险吗?
最后是一个短篇小说的记录。 1968年,ACM的通讯 发表了我的文本 标题“考虑的goto声明 有害的“,在以后的几年里会 最常被引用, 然而,令人遗憾的是,作者经常这样做 谁没有看到它比它的更多 标题,这成为了基石 成为模板我的名气:我们 会看到各种各样的文章 标题“X被认为是有害的” 几乎任何X,包括一个标题 “Dijkstra认为有害”。但 发生了什么事?我提交了一份 文章标题为“ 案件反对 goto语句 “,按顺序排列 加快出版速度 编辑改成了一封“给...的信” 编辑“,并在他的过程中 给它一个他自己的新头衔 发明!编辑是尼克劳斯 维尔特。
Donald E. Knuth撰写了一篇经过深思熟虑的关于这一主题的经典论文,与Dijkstra的论文相匹配,是Structured Programming with go to Statements。阅读都有助于重新建立背景和对主题的非教条性理解。在本文中,Dijkstra对此案的观点得到了报道,甚至更为强烈:
Donald E. Knuth:我相信通过提出这样一个 我实际上并不反对 因为Dijkstra的想法很尖锐 他最近写了以下内容: “请不要陷入陷阱 相信我很可怕 关于[去的 声明]。 我感到不舒服 感觉别人正在做 宗教走出它,好像是 编程的概念问题 可以用一个技巧来解决 一种简单的编码规则形式!“
答案 0 :(得分:236)
我的一位同事说使用GOTO的唯一原因是如果你把自己编程到一个角落,那是唯一的出路。换句话说,提前进行适当的设计,以后您不需要使用GOTO。
我认为这部漫画很精彩地说明了“我可以重组程序的流程,或者使用一点'GOTO'代替。”当你的设计薄弱时,GOTO是一种微弱的出路。 迅猛龙捕食弱者。
答案 1 :(得分:172)
以下陈述是概括;虽然总是可以恳求例外,但通常(根据我的经验和拙见)并不值得冒险。
上述脚注:
关于第2点,请考虑以下代码:
a = b + 1
/* do something with a */
在代码中的“做某事”点,我们可以高度自信地说明a
大于b
。 (是的,我忽略了未捕获的整数溢出的可能性。让我们不要陷入一个简单的例子。)
另一方面,如果代码以这种方式阅读:
...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...
获得标签10的多种方式意味着我们必须更加努力地对此时a
和b
之间的关系充满信心。 (事实上,在一般情况下,它是不可判定的!)
关于第4点,代码中“走向某个地方”的整个概念只是一个比喻。除了电子和光子(废热)之外,CPU内部的任何地方都没有“真正”的“走向”。有时我们会放弃另一个更有用的隐喻。我记得(几十年前)遇到的一种语言
if (some condition) {
action-1
} else {
action-2
}
是在虚拟机上实现的,它将action-1和action-2编译为外部无参数例程,然后使用单个双参数VM操作码,该操作码使用条件的布尔值来调用其中一个或另一个。这个概念只是“选择现在要调用的东西”,而不是“去这里或去那里”。再一次,只是改变隐喻。
答案 2 :(得分:126)
有时在单个函数中使用GOTO替代异常处理是有效的:
if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;
return;
err_cleanup:
...
COM代码似乎经常陷入这种模式。
答案 3 :(得分:116)
我只记得使用过一次goto。我有一系列五个嵌套计数循环,我需要能够根据某些条件从内部早期打破整个结构:
for{
for{
for{
for{
for{
if(stuff){
GOTO ENDOFLOOPS;
}
}
}
}
}
}
ENDOFLOOPS:
我可以轻松地声明一个布尔中断变量,并将其用作每个循环的条件的一部分,但在这个实例中,我认为GOTO同样实用且同样可读。
没有迅猛龙攻击我。
答案 4 :(得分:92)
我们已经有discussion,我支持my point。
此外,我厌倦了将高级语言结构描述为“goto
伪装”的人,因为他们显然没有得到点。例如:
即使是Scheme中的高级连续控制结构也可以被描述为复杂的转到。
这完全是胡说八道。 每个控制结构都可以用goto
来实现,但这种观察完全是微不足道的,毫无用处。 goto
因其积极影响而被认为是有害的,但由于其负面影响而已被结构化编程所消除。
同样地,说“GOTO是一种工具,并且作为所有工具,它可以被使用和滥用”完全不合适。没有现代建筑工人会使用岩石并声称它“是一种工具。”岩石已被锤子取代。 goto
已被控制结构取代。如果建筑工人在没有锤子的情况下被困在野外,当然他会使用岩石代替。如果程序员必须使用没有特征X的劣质编程语言,那么当然她可能不得不使用goto
。但如果她在其他任何地方使用它而不是相应的语言功能,她显然不能正确理解语言并错误地使用它。它真的很简单。
答案 5 :(得分:87)
Goto在我的列表中非常低,只是为了它而包含在程序中。这并不意味着它是不可接受的。
Goto可以用于状态机。循环中的switch语句(按典型重要性顺序):( a)实际上不代表控制流,(b)丑陋,(c)取决于语言和编译器,可能效率低下。所以你最终会为每个状态编写一个函数,并执行“return NEXT_STATE;”之类的操作。甚至看起来像goto。
当然,很难以一种易于理解的方式对状态机进行编码。然而,使用goto没有任何困难,并且通过使用替代控制结构不能减少任何困难。除非您的语言具有“状态机”构造。我没有。
在极少数情况下,您的算法在通过一系列允许转换(gotos)连接的节点(状态)序列的路径中最容易理解,而不是通过任何更具体的控制流(循环,条件,诸如此类,然后在代码中应该是显式的。你应该绘制一个漂亮的图表。
setjmp / longjmp可以很好地实现异常或类似异常的行为。虽然没有得到普遍赞扬,但例外通常被认为是“有效”的控制结构。
setjmp / longjmp比goto“更危险”,因为它们更难以正确使用,更不用说可理解了。
从来没有,也没有 永远是,任何语言 最难写的很难 码。 - 唐纳德克努特。
从C中取出goto不会让在C中编写好的代码变得更容易。事实上,它宁愿错过C 假设能够充当荣耀的汇编程序的观点。语言。
接下来它将是“被认为有害的指针”,然后“鸭子打字被视为有害”。然后,当他们拿走你不安全的编程结构时,谁会留下来为你辩护?是吗?
答案 6 :(得分:62)
在Kernel Trap的Linux: Using goto In Kernel Code中,与Linus Torvalds和一个关于在Linux代码中使用GOTO的“新人”进行了讨论。那里有一些很好的观点,Linus穿着那种平常的傲慢态度:)
一些段落:
Linus:“不,你被洗脑了 CS人认为尼克劳斯 Wirth实际上知道他是什么 谈论。他没有。他没有 有一个很好的线索。“
-
Linus:“我认为goto很好,而且 它们通常比可读性更强 大量的缩进。“
-
Linus:“当然,用愚蠢的语言 像Pascal一样,标签不可能 描述性的,goto可能很糟糕。“
答案 7 :(得分:49)
在C中,goto
仅在当前函数的范围内工作,这往往会定位任何潜在的错误。 setjmp
和longjmp
更加危险,非本地化,复杂化和依赖于实现。然而,在实践中,它们过于模糊,并且不常见,导致许多问题。
我认为C中goto
的危险性被夸大了。请记住,最初的goto
论证发生在像老式BASIC这样的语言时代,初学者会写这样的意大利面条代码:
3420 IF A > 2 THEN GOTO 1430
这里Linus描述了goto
:http://www.kernel.org/doc/Documentation/CodingStyle的恰当用法(第7章)。
答案 8 :(得分:45)
今天,我很难看到关于GOTO
声明的重要内容,因为“结构化编程”的人们大多赢得了争论,今天的语言有足够的控制流结构来避免GOTO
。
计算现代C程序中goto
的数量。现在添加break
,continue
和return
语句的数量。此外,请添加您使用if
,else
,while
,switch
或case
的次数。这就是如果你在1968年Dijkstra写信的时候用FORTRAN或BASIC写作,你的节目会有多少GOTO
。
当时的编程语言缺乏控制流程。例如,在最初的达特茅斯基础:
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
。
所以,你最终在程序中得到了{em>很多的GOTO
s。并且你不能依赖于GOTO
s在单个子例程中的限制(因为GOSUB...RETURN
是一个如此弱的子例程概念),所以这些GOTO
可以去任何地方。显然,这使控制流程难以遵循。
这是反GOTO
运动的来源。
答案 9 :(得分:34)
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);
现在功能上这段代码完全相同。实际上,编译器生成的代码几乎完全相同。然而,在程序员热衷于安抚 Nogoto (可怕的学术指责之神)的过程中,这个程序员完全打破了while
循环所代表的基本习语,并在其上做了一个真实的数字。代码的可读性。 这不是更好。
所以,故事的寓意是,如果你发现自己为了避免使用goto而采取真正愚蠢的事情,那就不要了。
答案 10 :(得分:30)
Donald E. Knuth在1992年CSLI的“文学编程”一书中回答了这个问题。在p。 17有一篇文章“Structured Programming with goto Statements”(PDF)。我认为这篇文章也可能已在其他书籍中发表过。
文章介绍了Dijkstra的建议,并描述了这种情况的有效性。但他也给出了许多反例(问题和算法),这些例子只能使用结构化循环来轻松复制。
本文包含问题的完整描述,历史,示例和反例。
答案 11 :(得分:23)
由Jay Ballou加入一个答案,我加上我的0.02英镑。如果Bruno Ranschaert还没有这样做,我会提到Knuth的“使用GOTO语句进行结构化编程”一文。
我没有看到过的一件事是Fortran教科书中教授的那些代码,虽然不常见,但却很常见。像DO循环和开放编码子程序的扩展范围(记住,这将是Fortran II,Fortran IV或Fortran 66 - 而不是Fortran 77或90)。语法细节至少有可能是不精确的,但概念应该足够准确。每种情况下的片段都在一个函数内。
请注意Kernighan&amp; amp;的优秀但过时(并绝版)的书“The Elements of Programming Style, 2nd Edn”。 Plauger包含了一些现实生活中滥用GOTO的例子,这些例子来自其时代(70年代后期)的编程教科书。但是,下面的材料不是那本书。
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。谷歌搜索assigned goto告诉我它已从Fortran 95中删除了。用一个结构化编程革命的粉笔可以说是公开使用Dijkstra的“GOTO Considered Harmful”字母或文章。
如果不了解在Fortran(以及其他语言,其中大部分已被正确地被淘汰)中所做的各种事情,我们新手很难理解Dijkstra所处理的问题的范围。用。哎呀,直到那封信发表十年后我才开始编程(但我确实不幸在Fortran IV中编程了一段时间)。
答案 12 :(得分:22)
转到认为有帮助。
我在1975年开始编程。对于20世纪70年代的程序员来说,“转向被认为是有害的”这些词或多或少地表示具有现代控制结构的新编程语言值得尝试。我们确实尝试过新语言。我们很快转换了。我们再也不回去了。
我们从未回过头来,但是,如果你年轻,那么你从来没有去过那里。
现在,除了作为程序员年龄的指标外,古代编程语言的背景可能不是很有用。尽管如此,年轻的程序员缺乏这种背景,因此他们不再理解在引入时向其目标受众传达
然而,这个特殊的口号“Goto被认为是有害的”已经成为了自己的不死生命。
可以不被滥用吗?答:当然可以,但那又怎样?实际上,每个编程元素都可能被滥用。例如,卑微的bool
比我们有些人想要的更容易被滥用。
相比之下,我不记得自1990年以来遇到一个单一的,实际的滥用滥用行为。
goto最大的问题可能不是技术问题,而是社交问题。有时候不太了解的程序员似乎觉得弃用goto会让他们听起来很聪明。您可能不得不满足这些程序员。这就是生活。
今天goto最糟糕的事情就是它用得不够好。
答案 13 :(得分:22)
没有 GOTO认为有害的。
GOTO是一个工具,作为所有工具,它可以被使用并且滥用。
然而,编程世界中有许多工具比使用更容易被滥用,而GOTO就是其中之一。 Delphi的 WITH 语句是另一个。
我个人不会在典型的代码中使用 ,但我对 GOTO 和 WITH 的奇怪用法是保证,替代解决方案将包含更多代码。
最好的解决方案是让编译器只是警告你关键字 tainted ,并且你必须在语句周围填写一些pragma指令以消除警告。
这就像告诉你的孩子不能用剪刀一起运行。剪刀也不错,但使用它们可能不是保持健康的最好方法。
答案 14 :(得分:17)
因为我开始在linux内核中做一些事情,所以不会像以前那样打扰我。起初我有点害怕看到他们(内核人员)在我的代码中添加了getos。我已经习惯于在某些有限的环境中使用gotos,现在偶尔也会使用它们。通常,它是一个转到函数末尾进行某种清理和纾困的goto,而不是在函数中的几个位置复制相同的清理和挽救。通常情况下,它不足以传递给另一个功能 - 例如释放一些局部(k)malloc'ed变量是一个典型的案例。
我编写的代码只使用了setjmp / longjmp一次。它是在MIDI鼓音序器程序中。回放发生在与所有用户交互不同的进程中,回放过程使用共享内存和UI进程来获取回放所需的有限信息。当用户想要停止播放时,播放过程只是做了一个“回到开头”的longjmp重新开始,而不是在用户希望它停止时执行的任何地方的一些复杂的展开。它工作得很好,很简单,在那种情况下我从来没有遇到任何问题或与之相关的错误。
setjmp / longjmp有它们的位置 - 但是那个地方是你不可能访问的地方,但是很长一段时间。
编辑:我只看了一下代码。它实际上是我使用的siglongjmp(),而不是longjmp(不是说这是一个大问题,但我忘记了siglongjmp甚至存在。)
答案 15 :(得分:16)
答案 16 :(得分:14)
如果您在C中编写VM,事实证明使用(gcc)计算得到这样的结果:
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;
}
比循环内的传统开关工作得快得多。
答案 17 :(得分:13)
goto
可用于混淆元编程 Goto
既是高级又是低级控件表达式,因此它没有合适的设计模式适合大多数问题。
它是低级,因为goto是一种原始操作,可以实现更高级的内容,如while
或foreach
或其他东西。
它是高级在某种意义上说,当它以某种方式使用时,它会以明确的顺序,以不间断的方式执行代码,除了结构化循环,并将其更改为具有足够goto
s的逻辑,可以动态地重新组装一堆逻辑。
因此,goto
有平淡和邪恶一面。
平淡无奇的一面是向上指向的goto可以实现一个完全合理的循环,向下指向的goto可以做一个完全合理的break
或return
。当然,实际的while
,break
或return
会更具可读性,因为穷人不需要模拟goto
的效果为了获得全局。所以,一般来说这是一个坏主意。
邪恶方涉及一个例程,不使用goto进行while,break或return,而是将其用于所谓的 spaghetti logic 。在这种情况下,goto-happy开发人员正在从goto的迷宫中构建代码片段,理解它的唯一方法是在整体上模拟它,当有很多goto时,这是一个非常累人的任务。我的意思是,想象一下评估代码的问题,其中else
并不恰好是if
的反转,其中嵌套的if
可能允许某些被外部if
拒绝的东西1}}等等。
最后,为了真正涵盖这一主题,我们应该注意到,除了Algol之外,基本上所有早期语言最初只根据其if-then-else
版本制作单个语句。因此,执行条件块的唯一方法是使用反条件围绕它goto
。疯了,我知道,但我读过一些陈旧的规格。请记住,第一台计算机是用二进制机器代码编程的,所以我认为任何一种HLL都是救星;我猜他们对HLL的具体功能并不太挑剔。
说过我曾经把所有的goto
都粘贴到我编写的每个程序中“只是为了惹恼纯粹主义者”。
答案 18 :(得分:10)
拒绝向程序员使用GOTO声明就像告诉木匠不要使用锤子,因为它可能会在锤击钉子时损坏墙壁。一个真正的程序员知道如何以及何时使用GOTO。我跟在其中一些所谓的“结构化程序”后面我看到这样的Horrid代码只是为了避免使用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
----------------------- OR ---------------------- < / p>
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
答案 19 :(得分:8)
“在此链接中http://kerneltrap.org/node/553/2131”
具有讽刺意味的是,消除goto引入了一个错误:省略了spinlock调用。
答案 20 :(得分:7)
原始论文应该被认为是“无条件的GOTO被认为是有害的”。它特别提倡基于条件(if
)和迭代(while
)构造的编程形式,而不是早期代码常见的测试和跳转。 goto
在某些语言或情况下仍然有用,因为没有适当的控制结构。
答案 21 :(得分:6)
关于我同意Goto 可以使用的唯一地方是当您需要处理错误时,每个特定点发生错误都需要特殊处理。
例如,如果您正在抓取资源并使用信号量或互斥量,则必须按顺序抓取它们,并且应始终以相反的方式释放它们。
有些代码需要非常奇怪的模式来获取这些资源,你不能只编写一个易于维护和理解的控制结构来正确处理这些资源的获取和释放,以避免死锁。
总是可以在没有goto的情况下正确地做到这一点,但在这种情况下和其他几个Goto实际上是更好的解决方案,主要是为了可读性和可维护性。
- 亚当
答案 22 :(得分:5)
直到C和C ++(以及其他罪犯)标记了中断并继续,goto将继续发挥作用。
答案 23 :(得分:5)
如果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可以做到吗?不。
答案 24 :(得分:5)
一个现代的GOTO用法是由C#编译器为yield yield定义的枚举数创建状态机。
GOTO应该由编译器而不是程序员使用。
答案 25 :(得分:4)
我从任何答案中看不到的一件事是,'goto'解决方案通常更高效,而不是经常提到的结构化编程解决方案之一
考虑多嵌套循环的情况,其中使用'goto'而不是一堆if(breakVariable)
部分显然更有效。解决方案“将你的循环置于函数中并使用返回”通常是完全不合理的。在循环使用局部变量的可能情况下,您现在必须通过函数参数传递它们,可能会处理由此产生的额外麻烦。
现在考虑清理案例,我经常使用它,并且非常常见,因为可能对许多语言中没有的try {} catch {}结构负责。完成相同操作所需的检查和额外变量的数量远远超过跳转的一个或两个指令,而且额外的功能解决方案根本不是解决方案。你无法告诉我,它更易于管理或更具可读性。
现在代码空间,堆栈使用和执行时间在许多情况下对许多程序员来说可能不够重要,但是当你处于只有2KB代码空间的嵌入式环境中时,需要避免50个字节的额外指令一个明确定义的'goto'只是可笑的,这并不像许多高级程序员所认为的那样罕见。
“goto有害”的说法对于向结构化编程非常有帮助,即使它总是过于概括。在这一点上,我们都听到它足够警惕使用它(我们应该)。当它显然是适合这项工作的工具时,我们不需要害怕它。
答案 26 :(得分:4)
我实际上发现自己被迫使用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在这里是合理的。
雨果
答案 27 :(得分:4)
几乎所有可以使用goto的情况,你都可以使用其他结构来做同样的事情。无论如何,编译器都会使用Goto。
我个人从不明确地使用它,也不需要。
答案 28 :(得分:4)
我避免使用它,因为同事或经理无疑会在代码审查中或在遇到错误时对其使用提出质疑。虽然我认为它有用(例如错误处理案例) - 但是你会遇到其他开发人员,他们会遇到某种类型的问题。
这不值得。
答案 29 :(得分:3)
您可以使用它来破坏深度嵌套的循环,但大多数情况下,您的代码可以重构为更清晰而没有深层嵌套循环。
答案 30 :(得分:3)
C ++包含构造函数和析构函数。这允许将模式称为RAII(资源分配是初始化)。基本上,您创建一个本地堆栈变量,创建堆栈变量的操作会打开一个文件,分配内存,锁定互斥锁,或以其他方式获取以后必须释放的资源。
当变量超出范围时,析构函数会运行并释放资源。
C没有此功能。但是你仍然经常需要在函数的开头获取资源,并在最后发布它们。
您的函数可能有一个或多个错误条件导致它提前返回。您不想复制资源发布代码。解决方案是使用goto。
示例:
int
foo(const char *arg)
{
char *argcopy = strdup(arg);
if (!isvalid(argcopy))
goto out1;
FILE *myfile = fopen(argcopy, "r");
if (myfile == NULL)
goto out1;
char bytes[10];
if (fread(bytes, sizeof(bytes), 1, myfile) != sizeof(mybytes))
goto out2;
/* do some actual work */
/* .... */
/* end of actual work */
out2:
fclose(myfile);
out1:
free(argcopy);
return 0;
}
答案 31 :(得分:3)
GOTO就像一把台锯,在采取适当的安全措施时非常有用。
我认为这是有害的,因为大多数初学者都会因桌锯和GOTO而失去手指。
在某些情况下,它是控制流量的唯一方法,但可以避免这些情况。
答案 32 :(得分:3)
是的,GOTO仍然被认为是有害的。当您发现自己处于使用GOTO可能有效的罕见情况时,您应该对自己的编程技能充满信心,而不需要对其他人进行验证。任何类似GOTO的函数都允许你在范围内跳得比GOTO允许的更远,应该被认为比GOTO更危险。
答案 33 :(得分:2)
虽然我认为最好避免在几乎任何情况下使用goto,但也有例外。 例如,我见过的一个地方goto语句是优雅的解决方案,与其他语言相比,更复杂的方法是为解释器实现尾调用elimintation。
答案 34 :(得分:2)
在我见过的每个平台上,高级控制结构都是低级别的(跳转)。例如,Java虚拟机有一个跳转字节代码,但没有if,else,while,for等等。
其中一些编译器会为简单的条件块创建意大利面条代码。
要回答您的问题,认为goto有害的人仍认为goto有害。 Goto很容易失去结构化编程的优势。
最后,这是你的计划;因此你的决定。我建议你不要使用goto,直到你能够自己回答你的问题,但是在特定问题的背景下。
答案 35 :(得分:1)
基本的想法是,goto给你太多的自由去做你不想做的事情。它可能会导致与goto语句无关的地方出现错误,因此会使代码维护变得更加困难。如果你认为你需要一个goto语句,那你就错了:)你应该重新考虑你的代码构造。这就是为什么现代编程语言为您提供了可读,可维护的流控制结构和异常处理机制的大量工作。
我也不同意lassevk。由于goto被滥用而不是正确使用,我相信它在设计良好的语言中没有地位。即使对于goto的“理想”用途,也应该首选其他需要更多代码的方法。
总而言之,是的,它仍被认为是有害的。
答案 36 :(得分:1)
许多现代编程语言使用他们的编译器来强制限制GOTO的使用 - 这减少了潜在的风险。例如,C#将不允许您使用GOTO从其外部跳入循环体。 Restrictions are mentioned in the documentation
这是GOTO有时比以前更安全的一个例子。
在某些情况下,使用GOTO与从函数早期返回(即早退出循环)相同。无论如何都可以提出良好的形式。
答案 37 :(得分:1)
在我看来,“转向有害”更多的是关于状态的封装和一致性。
许多代码,甚至是“oo”代码,都像任何意大利面条代码一样,具有糟糕的凌乱状态封装。
“goto被认为有害”的问题在于它让程序员只关注机械规则而不理解应该可用的唯一流量控制是返回方法的印象,这很容易导致传递通过引用大量陈述 - 并且 导致缺乏状态封装,“被认为有害”的东西试图摆脱。
按照典型的“OO”代码库中的控制流程告诉我,我们还没有意大利面条代码....(顺便说一句,我不是说'馄饨'代码通常会得到这么多讨厌 - 馄饨代码的执行路径通常非常简单,即使对象关系不是很明显)。
或者,换句话说,如果每个子程序只修改本地状态,那么除了通过那个子程序(或者至少是那个对象)之外,避免使用有利于所有作为子程序的东西才有用。
答案 38 :(得分:1)
我只需要在Basic(即VB,VBScript等)和批处理文件中使用它。然后我只用它来处理错误。在Basic中,我倾向于只使用“on error goto”。在批处理文件中,我必须使用它,因为没有else命令。然后我只将它们用作向前跳转到有意义的标签。
答案 39 :(得分:1)
goto有一些问题。一个是很难看出代码是如何流动的。由于花括号,更容易看到if-block,但是goto会隐藏你。此外,虽然和if也基本上是getos,但它们有助于解释为什么你在代码中来回跳转。有了常规的goto,你必须自己拼凑。
作为练习,尝试编写一些用于计算斐波那契序列的代码,看看完成后阅读的难度。
如果您打算使用该代码,那么我建议您编写一些单元测试并重写它。否则,就这样吧。
所有这一切,有时候,出于性能原因,使用goto可能是合适的。
答案 40 :(得分:1)
并不是说goto本身就是坏事;当相同的逻辑可以用另一种方式更清楚地表达时使用goto是不好的。它可能使代码很难遵循并使维护变得困难。只是去看看Bad Old Days中Basic的一些程序作为例子。
在我看来,在像C#这样的现代语言中,我们不应该在正常情况下需要goto。如果我发现自己在使用它,那通常表明我需要重新思考我的逻辑 - 几乎可以肯定的是,使用普通代码流语句表达相同代码的方式更为清晰。
那就是说是特殊目的,goto非常有用(而且我发现自己对没有它的语言感到恼火)。我主要在C中使用它来打破多级循环,或者用于错误处理;我相信C#具有语言功能,这意味着您不必这样做。 (它在生成自动生成的代码时也非常有用,但这不是大多数人在现实生活中遇到的。)
还有goto的另一个问题,这纯粹是政治问题:很多人讨厌它,并且在代码中使用它,即使是合理的,也可能导致问题。如果这是分配代码,那么是,重写它,或者你可能会被标记下来。否则我会倾向于将它留在下一次,直到你需要对该部分进行维护。
答案 41 :(得分:1)
有一次,在编程生涯的早期,我制作了一个由链中的一系列函数组成的程序,其中每个函数都称其成功条件和完成。
这是一个可怕的污泥,有多个严重的问题,最严重的是没有任何功能可以终止,直到其下的所有功能都终止。
但它很快就被开发出来,对于它设计要解决的有限问题很有效,并且明确地显示了程序的逻辑和流程,当我重构并扩展它以包含在另一个项目中时,它运行良好。
我的投票是在有意义的时候使用它,并在方便的时候尽快重构。
答案 42 :(得分:1)
goto
可能很有用,这里是用c++
编写的非常强大的开源国际象棋引擎鳕鱼的example。 goto
只是跳过一些条件检查(效率增益),如果不是goto
语句,程序将不得不这样做。如果goto
语句标签位于goto
声明之后,则它们非常无害且易读。
答案 43 :(得分:1)
使用goto可以轻松编写“意大利面条代码”,这种代码不是特别易于维护。要遵循的最重要的规则是编写可读代码,但当然这取决于项目的目标是什么。作为避免goto的“最佳实践”是一个好主意。这是极端编程类型称为“代码味道”的东西,因为它表明你可能做错了什么。在循环时使用break非常类似于goto,除了它不是goto,但是再次表明代码可能不是最佳的。这就是为什么,我相信,找不到更多现代编程漏洞也很重要,这些漏洞本质上是一个不同名称的转换。
答案 44 :(得分:0)
用于调度的计算器,通常比非常大的switch语句更容易理解。
对于错误和共线,我认为setcontext或setjmp(如果可用)是“更好”。
答案 45 :(得分:0)
在生成C状态机时,使用GOTO会很好。我永远不会在手写代码中使用GOTO - “现代”语言结构使它完全没必要。
setjmp / longjmp构造在某些情况下很有用(当缺少“true”异常时,或者当你实现类似Chicken方案的东西时),但它在“普通”编程中没有位置。
答案 46 :(得分:0)
在一个完美的世界里,我们永远不需要GOTO。但是,我们生活在一个不完美的世界。我们没有可以实现的每个控制结构的编译器。有时我觉得使用GOTO比使用实际上不存在的控制结构更好。
最常见的(不是常见的)是循环和半结构。你总是执行第一部分,也许你执行剩下的部分然后再回去再做第一部分。当然,您可以在while循环中使用布尔标志来实现它,但我不喜欢这个答案,因为在我看来它不太清楚。当你看到类似的东西时:
loop:
GetSomeData;
if GotData then
Begin
ProcessTheData;
StoreTheResult;
Goto Loop;
End;
对我来说,它比更清楚
Repeat
GetSomeData;
Flag := GotData;
if Flag then
Begin
ProcessTheData;
StoreTheResult;
End;
Until Not Flag;
有时候
Function GotTheData;
Begin
GetSomeData;
Result := GotData;
End;
While GotTheData do
Begin
ProcessTheData;
StoreTheResult;
End;
不是一个可行的答案,我坚信代码应该清楚。如果我必须发表评论来解释代码在做什么,我会考虑是否可以使代码更清晰并删除评论。
答案 47 :(得分:0)
看看this,它是GoTo的一个很好的用法,但是在使用垃圾收集器的语言中我认为使用GoTo的唯一原因是混淆你的代码(混淆工具使用GoTo隐藏他们的代码)
答案 48 :(得分:0)
Java String class源代码中的跳转示例:
int firstUpper;
/* Now check if there are any characters that need to be changed. */
scan: {
for (firstUpper = 0 ; firstUpper < count; ) {
char c = value[offset+firstUpper];
if ((c >= Character.MIN_HIGH_SURROGATE) &&
(c <= Character.MAX_HIGH_SURROGATE)) {
int supplChar = codePointAt(firstUpper);
if (supplChar != Character.toLowerCase(supplChar)) {
break scan;
}
firstUpper += Character.charCount(supplChar);
} else {
if (c != Character.toLowerCase(c)) {
break scan;
}
firstUpper++;
}
}
return this;
}
[... subsequent use of firstUpper ...]
这可以用很少的开销重写,例如:
int firstUpper = indexOfFirstUpper();
if (firstUpper < 0) return this;
即使在现代语言中,即使我实际上并不喜欢使用gotos,但我认为在许多情况下这些都是可以接受的,在像这样的低级别情况下,对我来说看起来更好(并且它做得多一点)而不只是退出循环)。
无意复活宗教战争。