使用'goto'语句不好吗?

时间:2012-08-10 16:53:34

标签: c# loops break goto

在做了一些关于如何突破二级循环的研究之后

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary loop
       // Do Something
       break; // Break main loop?
   }
}

大多数人建议调用'goto'函数
看如下例子:

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary Loop
       // Do Something
       goto ContinueOn; // Breaks the main loop
   }
}
ContinueOn:

然而;我经常听说'goto'声明是不好的做法。下图完美地说明了我的观点: Series found

所以

  • goto语句真的有多糟糕,为什么?
  • 是否有比使用'goto'语句更有效的方法来打破主循环?

8 个答案:

答案 0 :(得分:47)

编辑:

  

goto语句真的有多糟糕,为什么?

这取决于具体情况。我记不起任何时候我发现它使代码比重构更具可读性。它还取决于你个人对可读性的看法 - 有些人比其他人更不喜欢它,从其他答案可以清楚地看出。 (作为一个兴趣点,它广泛用于生成的代码 - C#5中的所有异步/等待代码都基于有效的许多getos。)

问题在于goto倾向于使用的情况是一种无论如何重构有助于事情的情况 - 而goto坚持使用解决方案随着代码变得越来越复杂,更难以遵循。

  

有没有比使用'goto'语句更有效的方法来打破主循环?

绝对。将您的方法提取到一个单独的函数中:

while (ProcessValues(...))
{
    // Body left deliberately empty
}

...

private bool ProcessValues()
{
   for (int i = 0; i < 15; i++)
   {
       // Do something
       return false;
   }
   return true;
}

我通常更喜欢这样做而不是引入一个额外的局部变量来跟踪“我已经完成” - 当然,这会起作用。

答案 1 :(得分:33)

  

goto语句真的有多糟糕,为什么?

由于所有正常原因,这真的很糟糕。在不支持它们的语言中模拟标记循环时,它是完美的。

在很多情况下,用函数替换它会分散逻辑,这些逻辑应该被理解为同一个单元。这使得阅读更加困难。 没有人喜欢跟踪一些功能,这些功能在行程结束时,当你有点遗忘时,并没有真正做任何事情 你从哪里开始。

用布尔值和一堆额外的ifs和break来代替它真的很笨重,并且更难以像真正的噪音那样遵循真实意图。

java(和javascript)中,这是完全可以接受的(标记为循环):

outer: while( true ) {
    for( int i = 0; i < 15; ++i ) {
        break outer;
    }
}

在C#中,看起来非常接近的等价物不是:

while( true ) {
   for (int I = 0; I < 15; I++) { 
       goto outer;
   }
}
outer:;

由于goto这个词,它具有一种心理作用,即让人们放弃所有常识,并使它们与上下文无关地链接xkcd。

  

有没有比使用'goto'语句更有效的方法来打破主循环?

在某些情况下没有,这就是为什么其他语言提供标记循环而C#提供goto的原因。请注意,您的示例太简单了 解决方案看起来并不太糟糕,因为它们是根据示例量身定制的。事实上,我也可以这样说:

   for (int I = 0; I < 15; I++) {
       break;
   }

这个怎么样:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            goto outer;
        }
        val = val / 2;
    }
}
outer:;

这对您来说仍然很好看:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    if (!Inner(i, ref val, len))
    {
        break;
    }
}

private bool Inner(int i, ref int val, int len)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            return false;
        }

        val = val / 2;
    }

    return true;
}

答案 2 :(得分:31)

我将非常不同意这里的所有其他答案。您使用goto呈现的代码没有任何问题。有一个原因 C#有一个goto语句,它恰好适用于您描述的这些类型的场景。

goto只是有一个负面的耻辱,因为在20世纪70年代和之前的人会编写可怕的,完全无法维护的代码,因为goto控制流在整个地方跳跃。 C#的goto甚至不允许在方法之间进行转换!然而,仍有这种不合理的耻辱。

在我看来,使用“现代”goto打破内循环绝对没有错。人们提供的“替代品”总是最终变得更加复杂,更难以阅读。

通常认为方法是可重用的。为循环的内部部分创建一个完全独立的方法,只能从该位置调用,并且方法实现可能最终位于源代码中的某个远程位置,这不是一种改进。

这种避免goto的愚蠢运动基本上等同于政治正确性,但在编程世界中。

答案 3 :(得分:7)

我同意大多数关于goto有多糟糕的答案。

我避免goto的消息是做这样的事情:

while (condition) { // Main Loop
   for (int i = 0; i < 15; i++) { // Secondary loop
       // Do Something
       if(someOtherCondition){
              condition = false // stops the main loop
              break; // breaks inner loop
       }
   }
}

答案 4 :(得分:6)

我有时会使用&#34; goto&#34;我发现它看起来不错,就像上面的例子一样;

bool AskRetry(Exception ex)
{
  return MessageBox.Show(you know here...) == DialogResult.Retry;
}

void SomeFuncOrEventInGui()
{
  re:try{ SomeThing(); }
  catch (Exception ex)
  {
    if (AskRetry(ex)) goto re;
    else Mange(ex); // or throw or log.., whatever...
  }
}

我知道你可以递归地做同样的事情,但谁关心它只是有效并且我使用。

答案 5 :(得分:4)

我的一位同事(他有15年以上的固件编程)并且我一直使用goto。但是,我们只用它来处理异常!!例如:

if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...

setsockopt_failed:
close(sd);

据我所知,这是处理C中异常的最佳方法。我相信,我也适用于C#。此外,我不是唯一一个这么认为的人:Examples of good gotos in C or C++

答案 6 :(得分:1)

要在这里添加其他答案,除了打破嵌套循环之外,使用goto的一种巧妙方法是简单干净的状态机:

goto EntryState;

EntryState:
//do stuff
if (condition) goto State1;
if (otherCondition) goto State2;
goto EntryState;

State1:
//do stuff
if (condition) goto State2;
goto State1;

State2:
//do stuff
if (condition) goto State1;
goto State2;

这是一种非常清晰易读的状态机处理方法,最重要的是它是免费的性能友好! 尽管无需goto即可执行此操作,但实际上只会使代码的可读性降低。不要回避goto,在使用它之前请三思。

答案 7 :(得分:0)

就我个人而言,我喜欢将goto视为“goto导致地狱”......并且在许多方面都是如此,因为它可能导致高度无法维护的代码和非常糟糕的做法。

话虽如此,它仍然是有原因的,并且在使用时,如果没有其他可用解决方案,应该谨慎使用。 OO语言本身并不需要它(很多)。

这些天我曾经看过它的唯一一次是混淆......一个使用goto从地狱创建代码的例子,所以人们将被阻止试图理解它!

某些语言依赖于等效的关键字。例如,在x86 assember中,您有关键字,如JMP(跳转),JE(如果等于跳转)和JZ(跳转为零)。这些在汇编语言中经常需要,因为在这个级别没有OO,并且很少有其他方法可以在应用程序中移动。

AFAIK ......除非绝对必要,否则请远离它。