我一直有一个印象,如果可能的话,永远不要使用goto。

然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。

在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。


当前回答

在Perl中,使用标签从循环中“goto”—使用“last”语句,这类似于break。

这样可以更好地控制嵌套循环。

也支持传统的goto标签,但我不确定是否有太多的实例,这是实现您想要的结果的唯一方法-子例程和循环应该足以满足大多数情况。

其他回答

在Perl中,使用标签从循环中“goto”—使用“last”语句,这类似于break。

这样可以更好地控制嵌套循环。

也支持传统的goto标签,但我不确定是否有太多的实例,这是实现您想要的结果的唯一方法-子例程和循环应该足以满足大多数情况。

Everybody who is anti-goto cites, directly or indirectly, Edsger Dijkstra's GoTo Considered Harmful article to substantiate their position. Too bad Dijkstra's article has virtually nothing to do with the way goto statements are used these days and thus what the article says has little to no applicability to the modern programming scene. The goto-less meme verges now on a religion, right down to its scriptures dictated from on high, its high priests and the shunning (or worse) of perceived heretics.

让我们把Dijkstra的论文放在背景中,对这个问题有一些了解。

When Dijkstra wrote his paper the popular languages of the time were unstructured procedural ones like BASIC, FORTRAN (the earlier dialects) and various assembly languages. It was quite common for people using the higher-level languages to jump all over their code base in twisted, contorted threads of execution that gave rise to the term "spaghetti code". You can see this by hopping on over to the classic Trek game written by Mike Mayfield and trying to figure out how things work. Take a few moments to look that over.

这就是Dijkstra在1968年的论文中谴责的“毫无节制地使用go to语句”。这就是他所生活的环境,促使他写出了那篇论文。在你的代码中,在你喜欢的任何地方跳转的能力是他所批评和要求停止的。将其与C或其他更现代的语言中goto的弱功能进行比较简直是可笑的。

我已经能听到信徒们面对异教徒时扬起的呐喊声了。“但是,”他们会念叨,“用c语言的goto会让代码变得很难读。”哦,是吗?如果没有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<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

也不去那里。因此它必须是可读的。

我举这些例子的重点是什么?并不是语言特性导致代码不可读、不可维护。这不是语法造成的。这是糟糕的程序员造成的。而糟糕的程序员,正如你在上面的项目中所看到的,可以使任何语言特性无法阅读和使用。比如上面的for循环。(你能看到他们,对吧?)

公平地说,有些语言结构比其他结构更容易被滥用。然而,如果你是一个C程序员,我会更仔细地观察#define大约50%的使用情况,然后才会开始反对goto!

因此,对于那些已经阅读到这里的人来说,有几个关键点需要注意。

Dijkstra's paper on goto statements was written for a programming environment where goto was a lot more potentially damaging than it is in most modern languages that aren't an assembler. Automatically throwing away all uses of goto because of this is about as rational as saying "I tried to have fun once but didn't like it so now I'm against it". There are legitimate uses of the modern (anaemic) goto statements in code that cannot be adequately replaced by other constructs. There are, of course, illegitimate uses of the same statements. There are, too, illegitimate uses of the modern control statements like the "godo" abomination where an always-false do loop is broken out of using break in place of a goto. These are often worse than judicious use of goto.

使用“goto”使你的代码更易于阅读或运行得更快。只是不要让它把你的代码变成意大利面条。

goto不好的一个原因是,除了编码风格之外,你可以用它来创建重叠但非嵌套的循环:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

这将创建一个奇怪的,但可能是合法的流控制结构,其中可能有(a, b, c, b, a, b, a, b, b,…)这样的序列,这让编译器黑客不高兴。显然,有许多聪明的优化技巧依赖于这种类型的结构不发生。(我应该检查一下我的龙书……)这样做的结果(使用一些编译器)可能是对包含gotos的代码没有进行其他优化。

如果你知道它只是“哦,顺便说一下”,恰好说服编译器发出更快的代码,那么它可能会很有用。就我个人而言,我更喜欢在使用像goto这样的技巧之前尝试向编译器解释什么是可能的,什么是不可能的,但可以说,我也可能在破解汇编程序之前尝试goto。

我遇到过goto是一个很好的解决方案的情况,我在这里或其他地方都没有看到过这个例子。

我有一个开关的情况下,所有的情况都需要调用相同的函数在最后。我有其他的情况,最后都需要调用不同的函数。

这看起来有点像这样:

switch( x ) {
    
    case 1: case1() ; doStuffFor123() ; break ;
    case 2: case2() ; doStuffFor123() ; break ;
    case 3: case3() ; doStuffFor123() ; break ;
    
    case 4: case4() ; doStuffFor456() ; break ;
    case 5: case5() ; doStuffFor456() ; break ;
    case 6: case6() ; doStuffFor456() ; break ;
    
    case 7: case7() ; doStuffFor789() ; break ;
    case 8: case8() ; doStuffFor789() ; break ;
    case 9: case9() ; doStuffFor789() ; break ;
}

我没有给每个case一个函数调用,而是用goto代替了break。goto跳转到一个标签,这也是在开关的情况下。

switch( x ) {
    
    case 1: case1() ; goto stuff123 ;
    case 2: case2() ; goto stuff123 ;
    case 3: case3() ; goto stuff123 ;
    
    case 4: case4() ; goto stuff456 ;
    case 5: case5() ; goto stuff456 ;
    case 6: case6() ; goto stuff456 ;
    
    case 7: case7() ; goto stuff789 ;
    case 8: case8() ; goto stuff789 ;
    case 9: case9() ; goto stuff789 ;
    
    stuff123: doStuffFor123() ; break ;
    stuff456: doStuffFor456() ; break ;
    stuff789: doStuffFor789() ; break ;
}

案例1到3都必须调用doStuffFor123(),类似的案例4到6必须调用doStuffFor456()等。

在我看来,如果你正确使用它们,gotos是非常好的。最后,任何代码都像人们写的那样清晰。有了gotos,你可以写出意大利面代码,但这并不意味着gotos是意大利面代码的原因。这个事业就是我们;程序员。如果我愿意,我也可以用函数创建意大利面条代码。宏也是如此。