我长期以来一直认为,goto
如果可能的话,永远不应该使用它.在前几天阅读libavcodec(用C语言编写)时,我注意到它的多种用途.goto
在支持循环和函数的语言中使用是否有利?如果是这样,为什么?
每个人都goto
直接或间接地反驳了Edsger Dijkstra的GoTo Considered Harmful文章,以证实他们的立场.太糟糕了Dijkstra的文章几乎与当前使用语句的方式无关goto
,因此文章所说的对现代编程场景几乎没有适用性.该goto
稀少梅梅现在近乎一种宗教,一直到它的经文从上决定的,它的大祭司和感知异端的回避(或更糟).
让我们把Dijkstra的论文放到上下文中,以便对这个主题有所了解.
当Dijkstra写他的论文时,当时流行的语言是非结构化的程序,如BASIC,FORTRAN(早期的方言)和各种汇编语言.使用高级语言的人在扭曲的,扭曲的执行线程中跳过他们的代码库是很常见的,这导致术语"意大利面条代码".你可以通过跳过迈克梅菲尔德写的经典Trek游戏并试图找出工作原理来看到这一点.花点时间看一下.
这是Dijkstra在1968年的论文中反对的"肆无忌惮地使用声明". 这就是他所生活的环境,这使他撰写了这篇论文.能够在您喜欢的任何时候在您的代码中随意跳转,这是他批评和要求停止的内容.将其与goto
C语言或其他此类更现代语言中的贫血力量进行比较,简直是可笑的.
当他们面对异教徒时,我已经可以听到邪教徒的颂歌了."但是,"他们会唱歌,"你可以用goto
C语言编写代码非常困难".哦耶?你可以在没有它的情况下使代码难以阅读goto
.像这个:
#define _ -F<00||--F-OO--; int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_ }
不是goto
在望,所以它必须易于阅读,对吧?或者这个怎么样:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k, l)char* *l;{g= atoi(* ++l); for(k= 0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<= g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1; while(d <=g){ ++O;for (f=0;f< O&&d<=g ;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f不
goto
存在要么.因此它必须是可读的.这些例子我的观点是什么?这不是语言功能,使得代码难以理解,无法维护.这不是语法.造成这种情况的是程序员.正如您在上面的项目中看到的那样,糟糕的程序员可以使任何语言功能都不可读和无法使用.就像
for
那里的循环一样.(你可以看到他们,对吧?)现在公平地说,一些语言结构比其他语言结构更容易被滥用.但是,如果你是一名C程序员,那么
#define
在我开始讨伐之前,我会更加密切地关注大约50%的用途goto
!因此,对于那些一直困扰着阅读的人来说,有几点需要注意.
Dijkstra的纸上
goto
声明是为编程环境下写goto
了一个很大 更多潜在的破坏性比在最现代的语言不属于汇编.
goto
由于这种情况,自动丢弃所有用途,就像说"我试过一次但是不喜欢它,所以现在我反对它"一样理性.
goto
代码中现代(贫血)陈述的合法用法无法被其他构造充分替代.当然,有相同陈述的非法使用.
现代控制语句也有非法使用,例如
godo
"憎恶",其中一个总是错误的do
循环被break
用来代替agoto
.这些往往比明智的使用更糟糕goto
.请记住,当你以一个接一个的方式投票给我时,我
goto
在过去的15到20年中已经在我自己的(非汇编)代码中使用了3次.我等待着愤怒的尖叫声和-1票,屏住呼吸.
为什么大家都在谈论Dijkstra的"去考虑有害"论文,而没有提到Knuth对它的回答,"结构化编程与陈述"?
@pocjoc:例如,在C中编写尾递归优化.这出现在功能语言解释器/运行时的实现中,但代表了一类有趣的问题.因此,用C语言编写的大多数编译器都使用goto.当然,它是一种在大多数情况下都不会出现的利基用途,但在需要它的情况下,使用起来很有意义.
-1为了说出一个非常大的论点,为什么Dijkstra的评论不适用,而忘记显示`goto`的优点实际上是什么(这是发布的问题)
@JLRishe你读得太深,甚至都没有.如果诚实地使用它的人认为它具有逻辑意义,那么这只是一个真正的逻辑谬误.但是他讽刺地说,清楚地表明他知道这很荒谬,这就是他表现出来的原因.他不是"用它来证明自己的观点"; 他正在用它来说明为什么说自动将代码写入意大利面条是荒谬的.即你仍然可以编写糟糕的代码而不用了.这是他的观点.而讽刺的错误二分法是他以幽默的方式说明它的方法.
@ Nietzche-jou"这是一个非常明显的稻草人[...]"他讽刺地使用错误的二分法来说明为什么耻辱是不合逻辑的.**一个稻草人**是一个逻辑谬误,你故意歪曲对手的立场,使他们的位置很容易被击败.例如"无神论者只是无神论者,因为他们讨厌上帝".**假二分法**是指当存在至少另一种可能性(即黑色和白色)时,相反的极端是正确的.例如"上帝讨厌同性恋者,因此他喜欢异性恋."
-1表示"其他事情都很糟糕,所以`goto`不能是"参数.我很确定反对`goto`的人也会反对你列出的代码.
...... @sgm已经说过:这是一大堆逻辑谬误.我只是想评论一件事:你说你过去x年曾使用过'goto` 3次 - 请告诉我,这与*any*其他"通用"语言功能相比如何(我明确排除了专门的,但有时必要的语言功能,如某些人根本不需要的"volatile"?一个*通用*语言特性在良好的代码中使用得如此之少,这不奇怪吗?对我来说,这是一个强烈的迹象,表明"goto"并没有带来太大的影响.即,它有缺陷.去搞清楚.
@JLRishe是的,他故意使用逻辑谬误*来引导读者得出一个有效的逻辑结论*.这有什么问题?你想说什么?因为这个原因,他的位置是否"不那么有效"?如果是这样,那么你的论证就是真正的错误二分法.想一想.你基本上说无论上下文如何,逻辑谬误都是无效的.如果一个人使用它,即使他们使用它作为插图,那么他们"自动错误".基本上,逻辑谬误=错误,没有逻辑谬误=正确.黑与白.没有灰色区域.这是一种错误的二分法.
我读了这篇文章为什么你不再在SO做出贡献并跳到这里.事实上,我完全同意"这不是使软件可读和可维护的语言(goto),而是你使用它的方式".八十年代中期,当我使用goto编写非常易读和可维护的代码时,我受到了批评,因为我只是转到了.我使用goto来模拟与FORTRAN中的datahidding结合的私有函数.几年后,一些人开始明白为什么我这样编程,因为这些东西在其他语言中变得普遍.Dijkstra的文章侧重于当时使用goto的方式
@Konrad Rudolph:让我重新评论你的评论,向你展示你思想中的缺陷."我明确地排除了会破坏我所谓的'论证'的语言特征,那么你如何证明这种其他语言特征呢?"
"在代码中有现代(贫血)goto语句的合法用法,不能被其他结构充分取代." 你能举一个例子吗?
"在我对goto进行讨伐之前很久,我对#define约50%的使用情况进行了更加密切的关注!" < - 事实上,你可以做到这两点!
@JUST MY正确的观点:这根本不像我说的那样.仅仅因为两个句子在语法上相似并不能使它们在语义上等效.`volatile`是一种非冗余的(即不能复制的)特殊语言功能.`goto`是通用的,与其他语言功能完全正交.
^像owlstead说的那样.我没有-1,但OP问的是,结果语言中goto是否有用.这并未给出goto何时有用的任何示例,甚至也没有链接到这些示例.此外,重点#3:这取决于语言,以及如何权衡权衡.通常,"更好"的解决方案是分解小功能,因此没有单个功能具有复杂的控制流.剩下的一个值得争辩的问题是,在这种小函数中使用多个回报是否可以接受.一些人改为一个共同的回归点.其他人使用旗帜和测试.
-1表示没有指定为什么在支持循环和函数的语言中使用`goto`是有利的单一原因.
我对这个答案进行投票的唯一原因是因为它是非常主观的回答.代码示例来自竞争,其中"智能"程序员故意编写混淆代码.请改写你的答案,删除逻辑谬误,然后你就得到了我的支持.
@ zxq9,你可以在C中执行尾递归,只需将函数体包装在循环中,这样`int f(int a,int b){if(a <10)返回a; 返回f(a + b,b); ``变成`int f(int a,int b){for(;;;){if(a <10){return a; } a + = b; 继续; 重写尾部调用以重新分配参数并将`continue`重写到循环顶部.
你的论点使用reductio ad absurdem - 你确实意识到你给出的例子来自竞争,难以阅读代码?????? -1
@B1KMusic"稻草人"可能不是正确的术语,但是讽刺与否,错误的二分法是一个谬误的论点,但这张海报试图用它来证明他的观点.
顺便说一下,来自@JUSTMYcorrectOPINION的答案就是现货.对goto的追捕只是停止了.我会进一步做出一个类似的说法,即对于函数/方法的多次返回的追捕应该同样消失,以及在C中使用像if(0 == x)`这样的保姆主义意外分配.人们,***它是2015年***,我们仍然被这个废话迷惑了?
2> Chris Gillum..:使用我所知道的"goto"语句有几个原因(有些人已经说过了):
干净地退出功能
通常在函数中,您可以分配资源并需要在多个位置退出.程序员可以通过将资源清理代码放在函数末尾来简化代码,函数的所有"退出点"都会转到清理标签.这样,您就不必在函数的每个"出口点"编写清理代码.
退出嵌套循环
如果你在一个嵌套循环中并且需要打破所有循环,那么goto可以使它比break语句和if-checks更清晰,更简单.
低级别的性能改进
这仅在perf-critical代码中有效,但goto语句执行速度非常快,并且在移动函数时可以提升.然而,这是一把双刃剑,因为编译器通常无法优化包含gotos的代码.
请注意,在所有这些示例中,gotos仅限于单个函数的范围.
@Jason - Bah.那是一股牛市.用`return`替换`goto`实在是太傻了.它不是"重构"任何东西,它只是"重命名",以便在"goto"压制环境中成长的人(即我们所有人)在使用道德上相当于"goto"时感觉更好.我更喜欢看到循环_我使用it_并看到一个小`goto`,它本身就是_just一个工具_,而不是看到有人为了避免使用`goto`而将循环移动到某个地方.
值得注意的是,所有`break`,`continue`,`return`基本上都是'goto`,只是包装精美.
有些场所很重要:例如,一个无异常的C++环境.在Silverlight源代码中,我们通过使用宏来存在数万(或更多)用于安全功能的goto语句 - 关键媒体编解码器和库通常通过返回值工作并且永远不会出现异常,并且很难将这些错误处理机制组合在一起一种高效的方式.
我没有看到`do {....} while(0)`应该是比goto更好的主意,除了它在Java中工作的事实.
退出嵌套循环的正确方法是将内循环重构为单独的方法.
对于干净利落的功能点,另一种方法是使用`do {/*code goes here*/} while(0);`并使用break(或continue:D)离开它.没有标签.
@DaanTimmer问题是,这通常导致代码非常难以理解,无论是深度嵌套还是一堆"辅助"变量.提前退出对于可读性而言是令人难以置信的 - 只要您的语言针对此进行了优化(例如,异常,(至少部分)管理的内存,......).当然,C#的`finally`声明使这变得轻而易举 - 一个更好的"包装器"来做任何必要的清理工作.我已经看到了一些使用`goto`的正当理由; 在所有情况下,我不得不重写与循环相同的东西(即使它不是循环!)只是为了避免编写可怕的`goto`.这伤害了可读性.
@Luaan,这些女巫狩猎显然仍在继续。荒谬的是,学生们把胡说八道当作福音带给了他们。人们为了避免goto而经历的扭曲是令人惊奇的。看看顶部的Jason评论。太神奇了……我真希望这种废话最终会在90年代消失。
3> dsimcha..:盲目地遵守最佳做法并非最佳做法.避免将
goto
语句作为流量控制的主要形式的想法是避免产生不可读的意大利面条代码.如果在适当的地方谨慎使用,它们有时可能是表达想法的最简单,最清晰的方式.Zortech C++编译器和D编程语言的创建者Walter Bright经常使用它们,但是明智地使用它们.即使有这些goto
陈述,他的代码仍然完全可读.底线:避免
goto
避免goto
是没有意义的.你真正想要避免的是生成不可读的代码.如果您的goto
-laden代码是可读的,那么它没有任何问题.
4> Konrad Rudol..:由于
goto
对程序流程的推理很难1(又称"意大利面条代码"),goto
通常只用于弥补缺失的特征:goto
实际使用可能是可以接受的,但只有当语言不提供更结构化的变体才能获得同样的目标.以怀疑为例:我们使用goto的规则是goto可以跳转到函数中的单个退出清理点.
这是正确的 - 但是只有当语言不允许使用清理代码(例如RAII或
finally
)进行结构化异常处理时,它才能更好地完成相同的工作(因为它是专门为执行它而构建的),或者当有充分的理由不这样做时采用结构化异常处理(但除非处于非常低的水平,否则你永远不会有这种情况).在大多数其他语言中,唯一可接受的用途
goto
是退出嵌套循环.即使在那里,将外环提升为自己的方法并使用它也几乎总是更好return
.除此之外,这
goto
是一个迹象,表明在特定的代码段中没有进行足够的考虑.
1支持
goto
实现某些限制的现代语言(例如,goto
可能不会跳入或跳出功能)但问题从根本上保持不变.顺便提一下,对于其他语言功能当然也是如此,最明显的例外.并且通常存在严格的规则,仅在指定时使用这些功能,例如不使用异常来控制非例外程序流的规则.
*"基本上,由于goto的缺陷性质(我认为这是无可争议的)"*-1我不再在那里阅读.
对goto的警告来自结构化编程时代,早期的回归也被认为是邪恶的.将嵌套循环移动到函数中会将一个"邪恶"换成另一个函数,并创建一个没有真正理由存在的函数(在"它允许我避免使用goto!"之外).确实,如果在没有约束的情况下使用goto是生成意大利面条代码的最强大工具,但这是一个完美的应用程序; 它简化了代码.Knuth为这种用法辩护,Dijkstra说:"不要陷入相信我对goto非常悲惨的陷阱".
`finally`?因此,除了错误处理之外的事情使用异常是好的,但使用`goto`是不好的?我认为*例外*非常合适.
这里好奇,但是使用gotos清理代码的情况又如何呢?通过清理,我的意思是,不仅要重新分配内存,还要说错误记录.我正在阅读一堆帖子,显然,没有人编写打印日志的代码..嗯?!
@Mud:在我遇到的情况下(每天),创建一个"没有真正存在理由"的函数**是最好的解决方案.原因:它从顶级函数中删除了细节,因此结果具有易于阅读的控制流程.我个人不会遇到goto产生最可读代码的情况.另一方面,我在简单函数中使用多个返回,其他人可能更喜欢转到单个退出点.我很想看到反例,其中goto比重构为命名良好的函数更具可读性.
@Christian`final`是在抛出异常后进行清理.这种虐待怎么样?
@Christian答案说完全按照我概述的方式使用RAII.我不会梦想在那种场景中使用异常(当然,除非它们实际上具有语义意义).我无法想象在那个答案中我会达到第3点的情况 - 我非常有信心,我从来没有这样做过.如果我曾经做过,我保证你会使用`goto`而不是`do ... while`结构.;-)
5> Viktor Sehr..:嗯,有一件事总是比
goto's
; 奇怪的是使用其他程序流操作符来避免goto:例子:
// 1 try{ ... throw NoErrorException; ... } catch (const NoErrorException& noe){ // This is the worst } // 2 do { ...break; ...break; } while (false); // 3 for(int i = 0;...) { bool restartOuter = false; for (int j = 0;...) { if (...) restartOuter = true; if (restartOuter) { i = -1; } } etc etc
@trinithis:如果它是"惯用的",那只是因为反goto邪教.如果仔细观察它,你会发现它只是一种说'goto after_do_block;`而不是实际说的那种方式.否则......一个只运行一次的"循环"?我称之为滥用控制结构.
@ Ignas2526你刚刚很好地展示了`#define`s是多次,比偶尔使用`goto`差很多次:D
@ThomasEding Eding你的观点有一个例外.如果你曾经做过一些C/C++编程并且不得不使用#define,你会知道,{} while(0)是封装多行代码的标准之一.例如:_#define do {memcpy(a,b,1); 某事++;}而(0)_比_#define memcpy(a,b,1)更安全,更好; 东西++ _
`do {} while(false)`我认为可以被认为是惯用的.你不能不同意:D
+1是一个很好的人们试图避免的方式列表,到极端; 我在其他地方看到过这种技术的提及,但这是一个很好的,简洁的清单.思考为什么在过去的15年里,我的团队从未需要任何这些技术,也没有使用过.即使在具有数十万行代码的客户端/服务器应用程序中,也由多个程序员完成.C#,java,PHP,python,javascript.客户端,服务器和应用程序代码.不是吹牛,也不是为一个职位改变宗教信仰,只是真正好奇为什么有些人遇到的情况要求最好的解决方案,而其他人则不...
6> Jakub Šturc..:在C# switch语句中,doest不允许掉线.因此goto用于将控制转移到特定的开关盒标签或默认标签.
例如:
switch(value) { case 0: Console.Writeln("In case 0"); goto case 1; case 1: Console.Writeln("In case 1"); goto case 2; case 2: Console.Writeln("In case 2"); goto default; default: Console.Writeln("In default"); break; }编辑:"没有通过"规则有一个例外.如果case语句没有代码,则允许直通.
这个答案很有趣 - C#删除了掉落,因为很多人认为它是有害的,而这个例子使用goto(也被许多人视为有害)来恢复原始的,据称有害的行为,但总体结果实际上不那么有害(因为代码清楚地说明了这个漏洞是有意的!).
仅当案例没有代码正文时.如果它有代码,那么你必须使用goto关键字.
只是因为用GOTO字母写的关键字不会使它成为goto.这个案例不是一个转到.它是一个用于fallthrough的switch语句结构.再说一次,我真的不太了解C#,所以我错了.
.NET 2.0支持切换功能 - http://msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
7> Chris Jester..:
#ifdef TONGUE_IN_CHEEK
Perl有一个
goto
允许你实现穷人的尾调用.:-Psub factorial { my ($n, $acc) = (@_, 1); return $acc if $n < 1; @_ = ($n - 1, $acc * $n); goto &factorial; }
#endif
好的,这与C无关
goto
.更严重的是,我同意其他关于使用goto
清理,或实现Duff设备等的评论.这都是关于使用,而不是滥用.(相同的注释可以适用于
longjmp
,例外call/cc
等等 - 它们具有合法用途,但很容易被滥用.例如,在完全非特殊情况下,抛出异常纯粹是为了逃避深层嵌套的控制结构.)
8> bugmagnet..:多年来,我写了几行汇编语言.最终,每种高级语言都会编译为getos.好吧,称他们为"分支"或"跳跃"或其他任何东西,但他们是必须的.任何人都可以写无转换汇编程序吗?
现在可以肯定的是,你可以向Fortran,C或BASIC程序员指出,与gotos一起进行骚乱是意大利面食的一个秘方.答案不是要避免它们,而是要小心使用它们.
刀可用于准备食物,释放某人或杀死某人.我们害怕后者吗?同样地,goto:不经意地使用它会阻碍,小心使用它会有所帮助.
无论如何,任何提出"这一切都归结为JMP的人"!论证基本上不理解高级语言编程背后的要点.
你真正需要的只是减法和分支.其他一切都是为了方便或表现.
9> herohuyongta..:看看在C语言编程时何时使用Goto:
虽然使用goto几乎总是糟糕的编程习惯(当然你可以找到一种更好的XYZ方式),但有时它确实不是一个糟糕的选择.有些人甚至认为,当它有用时,它是最好的选择.
关于goto的大部分内容实际上只适用于C.如果你使用的是C++,那么就没有理由使用goto来代替异常.但是,在C语言中,您没有异常处理机制的强大功能,因此如果您想将错误处理与其余程序逻辑分开,并且希望避免在整个代码中多次重写清理代码,然后goto可能是一个不错的选择.
我的意思是什么?您可能有一些看起来像这样的代码:
int big_function() { /* do some work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* clean up*/ return [success]; }在您意识到需要更改清理代码之前,这很好.然后你必须经历并进行4次更改.现在,您可能决定将所有清理封装到单个函数中; 这不是一个坏主意.但它确实意味着您需要小心指针 - 如果您打算在清理函数中释放指针,除非您传入指针指针,否则无法将其设置为指向NULL.在很多情况下,无论如何你都不会再使用那个指针,所以这可能不是一个主要问题.另一方面,如果你添加一个新的指针,文件句柄或其他需要清理的东西,那么你需要再次更改你的清理功能; 然后你需要改变该函数的参数.
通过使用
goto
,它将是int big_function() { int ret_val = [success]; /* do some work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } end: /* clean up*/ return ret_val; }这样做的好处是,您的代码可以访问执行清理所需的所有内容,并且您已设法大大减少了更改点的数量.另一个好处是你已经从你的功能的多个出口点变成了一个; 如果没有清理,你就不会意外地从功能中返回.
此外,由于goto仅用于跳转到单个点,因此并不是说您要创建大量的意大利面条代码来回尝试模拟函数调用.相反,goto实际上有助于编写更结构化的代码.
总之,
goto
应该谨慎使用,并作为最后的手段 - 但它有一个时间和地点.问题不应该是"你必须使用它",而是"它是使用它的最佳选择".
10> Anders Euren..:其中一个原因是goto很糟糕,除了编码风格之外,你可以用它来创建重叠但非嵌套的循环:
loop1: a loop2: b if(cond1) goto loop1 c if(cond2) goto loop2这将创建奇怪但可能合法的控制流结构,其中像(a,b,c,b,a,b,a,b,...)这样的序列是可能的,这使得编译器黑客不高兴.显然,有许多聪明的优化技巧依赖于这种未发生的结构.(我应该检查我的龙书副本......)这可能(使用一些编译器)的结果是对于包含
goto
s的代码没有进行其他优化.如果您只是知道它,"哦,顺便说一下",恰好可以说服编译器发出更快的代码.就个人而言,我更愿意尝试向编译器解释在使用像goto之类的技巧之前可能会发生什么以及什么不可能,但可以说,我可能也会
goto
在黑客汇编程序之前尝试.
任何语言结构都可能被滥用,使其难以理解或表现不佳.
嗯...带我回到在一家金融信息公司编程FORTRAN的日子.2007年
我认为`goto`有用*的原因之一是它允许你构造这样的循环,否则需要一堆逻辑扭曲.我进一步争辩说,如果优化器不知道如何重写它,那么*good*.这样的循环不应该是为了性能或可读性,而是因为这正是事物需要发生的顺序.在这种情况下,我不会特别*希望*优化器拧紧它.
11> Ricardo..:我觉得有趣的是,有些人会尽可能地列出可以接受goto的情况,并说所有其他用途都是不可接受的.你真的认为你知道goto是表达算法的最佳选择吗?
为了说明,我将举例说明这里还没有人展示过:
今天我正在编写用于在哈希表中插入元素的代码.哈希表是先前计算的缓存,可以随意覆盖(影响性能但不是正确性).
哈希表的每个桶都有4个槽,我有一堆标准来决定在桶满时要覆盖哪个元素.现在这意味着最多可以通过一个桶进行三次传递,如下所示:
// Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) goto add; // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) goto add; // Additional passes go here... add: // element is written to the hash table here现在,如果我没有使用goto,这段代码会是什么样的?
像这样的东西:
// Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) break; if (add_index >= ELEMENTS_PER_BUCKET) { // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) break; if (add_index >= ELEMENTS_PER_BUCKET) // Additional passes go here (nested further)... } // element is written to the hash table here如果添加更多传递,它会变得越来越糟,而带有goto的版本始终保持相同的缩进级别,并且避免使用伪结果if语句,其结果由前一个循环的执行所暗示.
所以还有另一种情况,goto使代码更清晰,更容易编写和理解......我确信还有更多,所以不要假装知道goto有用的所有情况,解散你不能的任何好的没想到.
事实上,任何人都必须举例说明某些算法应该如何自然地使用goto - 这是一个令人遗憾的反思今天的算法思维很少!当然,@里卡多的例子是(很多其中之一)goto优雅而明显的完美例子.
你还没有真正解释如何添加更多函数摆脱goto,多个缩进级别和虚假if语句......
12> Ari Pernick..:我们使用goto的规则是goto可以跳转到函数中的单个退出清理点.在非常复杂的功能中,我们放宽了规则以允许其他跳跃前进.在这两种情况下,我们都避免使用经常出现错误代码检查的深层嵌套if语句,这有助于提高可读性和维护性.
13> 小智..:goto语句,其合法的用途,和替代结构,可以代替“良性goto语句”中使用,但可能会被滥用一样容易goto语句,是高德纳的文章“最周到和全面的讨论与goto语句结构化编程 ” ,在1974年12月的计算机调查中(第6卷第4期,第261-301页)。
毫不奇怪,这份已有39年历史的论文的某些方面是过时的:处理能力的数量级增加使得Knuth的性能改进在中等大小的问题上不明显,并且从那时起发明了新的编程语言结构。(例如,try-catch块包含Zahn的Construct,尽管很少以这种方式使用。)但是Knuth涵盖了论点的方方面面,在任何人再次讨论该问题之前,都应阅读该书。