61

在对如何突破二级循环进行了一些研究之后

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”语句是不好的做法。下图完美地说明了我的观点: 找到系列

所以

  • goto 语句到底有多糟糕,为什么?
  • 有没有比使用“goto”语句更有效的方法来打破主循环?
4

7 回答 7

55

编辑:

goto 语句到底有多糟糕,为什么?

这取决于具体情况。我不记得我发现它使代码比重构更具可读性的任何时候。这还取决于您对可读性的个人看法 - 有些人比其他人更不喜欢它,从其他答案中可以清楚地看出。(作为一个兴趣点,它广泛用于生成的代码 - C# 5 中的所有 async/await 代码实际上都基于大量的 goto)。

问题在于,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;
}

我通常更喜欢这样做而不是引入一个额外的局部变量来跟踪“我完成了吗”——当然这也可以。

于 2012-08-10T16:56:04.643 回答
50

我将强烈反对这里的所有其他答案。您使用的代码goto没有任何问题。C# 有一个声明是有原因goto的,它正是针对您描述的这些类型的场景。

goto只是有一个负面的污名,因为在 1970 年代和之前的人们会编写可怕的、完全不可维护的代码,其中控制流会因为goto. C#goto甚至不允许在方法之间转换!然而,这种非理性的污名仍然存在。

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

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

于 2012-08-10T18:10:44.483 回答
43

goto 语句到底有多糟糕,为什么?

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

在许多情况下,用函数替换它会分散真正应该作为同一个单元读取的逻辑。这使得阅读变得更加困难。没有人喜欢跟踪那些直到旅程结束时才真正做任何事情的函数,当你有点忘记你从哪里开始的时候。

用布尔值和一堆额外的 if 和 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;
}
于 2012-08-11T10:00:40.267 回答
11

我有时使用“goto”,我发现它看起来不错,就像上面的例子;

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...
  }
}

我知道你可以递归地做同样的事情,但谁在乎它只是工作,我使用。

于 2014-12-01T10:08:03.480 回答
8

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

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

setsockopt_failed:
close(sd);

据我所知,这是在 C 中处理异常的最佳方式。我相信,我也为 C# 工作。另外,我不是唯一一个这样认为的人:C 或 C++ 中好的 goto 示例

于 2013-08-16T09:08:36.030 回答
5

为了在这里添加其他答案,除了打破嵌套循环之外,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,在使用它之前请考虑一下。

于 2019-08-03T17:25:13.680 回答
-2

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

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

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

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

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

于 2012-08-10T22:32:56.950 回答