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

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

在支持循环和函数的语言中使用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是意大利面代码的原因。这个事业就是我们;程序员。如果我愿意,我也可以用函数创建意大利面条代码。宏也是如此。

其他回答

在c#中,switch语句不允许切换。因此,goto用于将控制转移到特定的开关箱标签或默认标签。

例如:

switch(value)
{
  case 0:
    Console.WriteLine("In case 0");
    goto case 1;
  case 1:
    Console.WriteLine("In case 1");
    goto case 2;
  case 2:
    Console.WriteLine("In case 2");
    goto default;
  default:
    Console.WriteLine("In default");
    break;
}

编辑:有一个例外的“不掉落”规则。如果case语句没有代码,则允许延迟执行。

嗯,有一件事总是比goto更糟糕;奇怪地使用其他程序流操作符来避免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

在Perl模块中,有时希望动态地创建子例程或闭包。问题是,一旦你创建了子例程,你如何得到它。你可以直接调用它,但是如果子例程使用caller(),那么它就没有那么有用了。这就是goto &子例程变化可能有用的地方。

这里有一个简单的例子:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

您还可以使用这种形式的goto来提供尾部调用优化的基本形式。

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

(在Perl 5 version 16中,最好写成goto __SUB__;)

有一个模块会导入尾修饰符,如果你不喜欢使用这种形式的goto,还有一个模块会导入递归。

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

使用goto的大多数其他原因都可以用其他关键字更好地完成。

比如重写一段代码:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

或者从多个地方找到最后一段代码:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

如果有,为什么?

C语言没有多级/标记的中断,并不是所有的控制流都可以用C语言的迭代和决策原语轻松建模。Gotos对纠正这些缺陷大有帮助。

有时使用某种类型的标志变量来实现一种伪多级中断更清晰,但它并不总是优于goto(至少goto可以轻松地确定控制的位置,不像标志变量),有时您只是不想为了避免goto而付出旗帜/其他扭曲的性能代价。

Libavcodec是一段性能敏感的代码。控制流的直接表达可能是优先考虑的,因为它往往会运行得更好。

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

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

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