在这个主题中,我们看一下goto
C或C++ 的良好用法示例.它的灵感来自人们投票的答案,因为他们认为我在开玩笑.
摘要(标签从原始标签更改为使意图更清晰):
infinite_loop: // code goes here goto infinite_loop;
为什么它比替代品更好:
这是具体的. goto
是导致无条件分支的语言构造.替代方案取决于使用支持条件分支的结构,具有退化的始终为真的条件.
标签记录了意图,没有额外的评论.
读者不必扫描早期介入代码break
S(尽管它仍然可能无原则黑客模拟
continue
与早期goto
).
规则:
假装gotophobes没有赢.据了解,上述内容不能用于实际代码中,因为它违背了既定惯用语.
假设我们都听说'Goto被认为有害',并且知道goto可以用来写意大利面条代码.
如果你不同意一个例子,那就单独批评技术优点('因为人们不喜欢goto'不是技术原因).
让我们看看我们是否可以像成年人一样谈论这个问题.
编辑
这个问题现在好了.它产生了一些高质量的答案.感谢大家,尤其是那些认真对待我的小循环示例的人.大多数怀疑论者都担心缺乏范围.正如@quinmars在评论中指出的那样,你总是可以在循环体上放置大括号.我顺便注意到for(;;)
并且while(true)
也没有给你免费的大括号(并且省略它们会导致烦恼的错误).无论如何,我不会浪费任何更多的脑力这个小事-我可以用无害的习惯生活for(;;)
和while(true)
(一样好,如果我想继续我的工作).
考虑到其他反应,我发现许多人认为goto
你总是需要以另一种方式重写.当然,你可以goto
通过引入一个循环,一个额外的标志,一堆嵌套的if
s或其他什么来避免,但为什么不考虑是否goto
可能是这项工作的最佳工具?换句话说,人们准备忍受多少丑陋以避免使用内置语言功能达到预期目的?我的看法是,即使添加一面旗帜,也要付出太高的代价.我喜欢我的变量来表示问题或解决方案域中的事物.'唯一要避免goto
'不削减它.
我会接受第一个给出C模式分支到清理块的答案.国际海事组织,这是goto
所有已发布答案的最强情况,当然,如果你通过一个仇恨必须通过避免它的扭曲来衡量它.
这是我听说人们使用的一个技巧.我从来没有在野外见过它.它只适用于C,因为C++有RAII来更加惯用.
void foo() { if (!doA()) goto exit; if (!doB()) goto cleanupA; if (!doC()) goto cleanupB; /* everything has succeeded */ return; cleanupB: undoB(); cleanupA: undoA(); exit: return; }
C中GOTO的经典需求如下
for ... for ... if(breakout_condition) goto final; final:
没有goto,没有直接的方法来突破嵌套循环.
这是我的非愚蠢的例子(来自Stevens APITUE),用于Unix系统调用,可能会被信号中断.
restart: if (system_call() == -1) { if (errno == EINTR) goto restart; // handle real errors }
替代方案是简并循环.这个版本读起来像英语"如果系统调用被信号中断,重新启动它".
如果Duff的设备不需要goto,那么你也不应该!;)
void dsend(int count) { int n; if (!count) return; n = (count + 7) / 8; switch (count % 8) { case 0: do { puts("case 0"); case 7: puts("case 7"); case 6: puts("case 6"); case 5: puts("case 5"); case 4: puts("case 4"); case 3: puts("case 3"); case 2: puts("case 2"); case 1: puts("case 1"); } while (--n > 0); } }
维基百科条目上面的代码.
Knuth写了一篇论文"使用GOTO语句进行结构化编程",你可以从这里得到它.你会在那里找到很多例子.
我一般都没有反对任何东西,但我可以想到几个原因,为什么你不想像你提到的那样将它们用于循环:
它不限制范围,因此您在内部使用的任何临时变量都不会在以后释放.
它不限制范围因此可能导致错误.
它不限制范围,因此您不能在以后的代码中重复使用相同的变量名称.
它不限制范围,因此您有机会跳过变量声明.
人们不习惯它,它会使你的代码更难阅读.
这种类型的嵌套循环可能导致意大利面条代码,法线循环不会导致意大利面条代码.
很普通的.
do_stuff(thingy) { lock(thingy); foo; if (foo failed) { status = -EFOO; goto OUT; } bar; if (bar failed) { status = -EBAR; goto OUT; } do_stuff_to(thingy); OUT: unlock(thingy); return status; }
我曾经使用过的唯一一个例子goto
就是向前跳,通常是用块跳,而不是用块.这避免了滥用do{}while(0)
和其他增加嵌套的构造,同时仍然保持可读的结构化代码.
使用goto的一个好地方是可以在几个点中止的程序,每个点都需要不同级别的清理.Gotophobes总是可以用结构化代码和一系列测试替换gotos,但我认为这更简单,因为它消除了过多的缩进:
if (!openDataFile()) goto quit; if (!getDataFromFile()) goto closeFileAndQuit; if (!allocateSomeResources) goto freeResourcesAndQuit; // Do more work here.... freeResourcesAndQuit: // free resources closeFileAndQuit: // close file quit: // quit!
@ fizzer.myopenid.com:您发布的代码段等效于以下内容:
while (system_call() == -1) { if (errno != EINTR) { // handle real errors break; } }
我绝对喜欢这种形式.
尽管随着时间的推移我已经开始讨厌这种模式,但它已经融入了COM编程中.
#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error} ... HRESULT SomeMethod(IFoo* pFoo) { HRESULT hr = S_OK; IfFailGo( pFoo->PerformAction() ); IfFailGo( pFoo->SomeOtherAction() ); Error: return hr; }
这是一个很好的转到的例子:
// No Code