79

在这个线程中,我们看一下goto在 C 或 C++ 中良好使用的示例。它的灵感来自人们投票赞成的答案,因为他们认为我在开玩笑。

摘要(标签从原来的改变,使意图更加清晰):

infinite_loop:

    // code goes here

goto infinite_loop;

为什么它比替代品更好:

  • 这是具体的。 goto是导致无条件分支的语言结构。替代方案取决于使用支持条件分支的结构,具有退化的始终为真条件。
  • 标签记录了意图,没有额外的注释。
  • 读者不必扫描 early breaks 的介入代码(尽管没有原则的黑客仍然可以 continue使用 early进行模拟goto)。

规则:

  • 假装gotophobes没有赢。据了解,以上内容不能在实际代码中使用,因为它违反了既定的习惯用法。
  • 假设我们都听说过“Goto 被认为有害”并且知道 goto 可用于编写意大利面条式代码。
  • 如果您不同意某个示例,请仅根据技术优点批评它(“因为人们不喜欢 goto”不是技术原因)。

让我们看看我们是否可以像成年人一样谈论这个问题。

编辑

这个问题现在似乎结束了。它产生了一些高质量的答案。感谢大家,尤其是那些认真对待我的小循环示例的人。大多数怀疑论者担心缺乏块范围。正如@quinmars 在评论中指出的那样,您始终可以在循环体周围放置大括号。我顺便指出for(;;)while(true)也不要免费给你大括号(省略它们会导致令人烦恼的错误)。无论如何,我不会再在这件小事上浪费你的脑力——我可以忍受无害和惯用的for(;;)while(true)(如果我想保住工作也一样)。

考虑到其他回应,我看到许多人认为goto你总是必须以另一种方式重写。当然你可以goto通过引入一个循环、一个额外的标志、一堆嵌套if的 s 或其他什么来避免 a,但是为什么不考虑是否goto可能是这项工作的最佳工具呢?换句话说,人们准备忍受多少丑陋以避免将内置语言功能用于其预期目的?我的看法是,即使添加一面旗帜也付出了高昂的代价。我喜欢我的变量来代表问题或解决方案域中的事物。'仅仅为了避免goto'并没有削减它。

我会接受第一个答案,它给出了用于分支到清理块的 C 模式。IMO,这为goto所有已发布的答案提供了最有力的案例,当然,如果您通过扭曲来衡量它,那么仇恨者必须通过这些扭曲来避免它。

4

16 回答 16

87

这是我听说人们使用的一个技巧。不过我从来没有在野外见过它。它仅适用于 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;
}
于 2008-10-29T04:08:39.463 回答
85

C中GOTO的经典需求如下

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

没有 goto 就没有直接的方法来打破嵌套循环。

于 2008-10-29T04:31:43.787 回答
32

这是我的非愚蠢示例(来自 Stevens APITUE),用于可能被信号中断的 Unix 系统调用。

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

另一种方法是退化循环。这个版本读起来像英语“如果系统调用被信号中断,请重新启动它”。

于 2008-10-29T04:07:39.293 回答
14

如果 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);
    }
}

以上代码来自维基百科条目

于 2008-10-29T04:08:23.723 回答
14

Knuth 写了一篇论文“Structured programming with GOTO statements”,你可以从这里得到它。你会在那里找到很多例子。

于 2008-10-29T07:17:10.097 回答
12

一般来说,我对 goto 没有任何反对意见,但我可以想到几个原因,为什么你不想像你提到的那样将它们用于循环:

  • 它不限制范围,因此您在内部使用的任何临时变量直到稍后才会被释放。
  • 它不限制范围,因此可能导致错误。
  • 它不限制范围,因此您不能在以后的代码中在同一范围内重复使用相同的变量名。
  • 它不限制范围,因此您有机会跳过变量声明。
  • 人们不习惯它,它会使你的代码更难阅读。
  • 这种类型的嵌套循环会导致意大利面条代码,法线循环不会导致意大利面条代码。
于 2008-10-29T04:01:57.527 回答
12

很普通的。

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)和其他增加嵌套的构造,同时仍保持可读的结构化代码。

于 2008-10-29T04:18:02.097 回答
7

使用 goto 的一个好地方是在可以在多个点中止的过程中,每个点都需要不同级别的清理。Gotophobes 总是可以用结构化代码和一系列测试来替换 goto,但我认为这更直接,因为它消除了过多的缩进:

如果(!openDataFile())
  退出;

如果(!getDataFromFile())
  转到关闭文件并退出;

if (!allocateSomeResources)
  转到免费资源并退出;

// 在这里做更多的工作....

freeResourcesAndQuit:
   // 释放资源
关闭文件并退出:
   // 关闭文件
辞职:
   // 辞职!
于 2008-10-29T04:09:17.607 回答
7

@fizzer.myopenid.com:您发布的代码片段相当于以下内容:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

我绝对喜欢这种形式。

于 2008-10-29T04:15:40.683 回答
6

尽管随着时间的推移我越来越讨厌这种模式,但它已经根植于 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;
}
于 2008-10-29T05:07:00.720 回答
5

这是一个很好的 goto 示例:

// No Code
于 2008-10-29T04:01:13.160 回答
1

我已经看到 goto 使用正确,但情况通常很丑陋。只有当它goto本身的使用比原来的差很多时。@Johnathon Holland 问题是你的版本不太清楚。人们似乎害怕局部变量:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

我更喜欢这样的循环,但有些人更喜欢while(true).

for (;;)
{
    //code goes here
}
于 2008-10-29T11:09:54.527 回答
0

我对此的抱怨是你失去了块范围;如果循环中断,则在 goto 之间声明的任何局部变量仍然有效。(也许您假设循环永远运行;不过,我认为这不是最初的问题作者要问的。)

作用域的问题更多地是 C++ 的问题,因为某些对象可能取决于它们的 dtor 在适当的时间被调用。

对我来说,使用 goto 的最佳理由是在多步骤初始化过程中,如果一个失败,所有初始化都退出是至关重要的,例如:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;
于 2008-10-29T04:11:44.793 回答
0

我自己不使用 goto,但是我曾经与一个在特定情况下会使用它们的人一起工作。如果我没记错的话,他的理由是围绕性能问题——他也有具体的规则。始终在同一个函数中,并且标签始终位于 goto 语句的下方。

于 2008-10-29T05:53:48.753 回答
0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}
于 2014-01-09T21:58:52.753 回答
-1

@格雷格:

为什么不这样做:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
于 2008-10-29T04:15:06.610 回答