这证明goto语句合理吗?


15

一秒钟前,我遇到了这个问题,我从中提出了一些建议:“ break n”结构有名称吗?

对于人们来说,这是一种不必要的复杂方法,它可以指示程序摆脱双重嵌套的for循环:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

我知道教科书喜欢说goto语句是魔鬼,而我本人一点也不喜欢它们,但这是否有例外的理由?

我正在寻找与n嵌套for循环有关的答案。

注意:无论您回答是,否还是介于两者之间,都不欢迎完全封闭的答案。特别是如果答案是否定的,请提供一个合理的正当理由(无论如何,这与Stack Exchange法规相距不远)。


2
如果您可以这样编写内循环条件,for (j = 10; j > 0 && !broken; j--)则不需要该break;语句。如果broken在外部循环开始之前将的声明移到,那么如果可以这样编写,for (i = 0; i < 10 && !broken; i++)那么我认为没有 break;必要。
FrustratedWithFormsDesigner 2014年

8
给出的例子是从C# -在C#参考goto后藤(C#参考) - “goto语句也很有用得到深度嵌套循环了。”

天哪,我不知道c#有goto语句!您在StackExchange上学到的东西。(我不记得我最后一次希望使用goto语句功能,但似乎从未出现过)。
John Wu

6
恕我直言,这实际上取决于您的“编程宗教”和当前的月相。对我(以及我周围的大多数人)而言,如果循环很小,使用goto跳出深层嵌套是很好的选择,而另一种选择是多余地检查只是为了使循环条件中断而引入的bool变量。更重要的是,当您想从主体中途突破时,或者在最内层循环之后,也需要一些代码来检查布尔值。
PlasmaHH 2014年

1
goto实际上,在第一种情况下执行任何代码后,C#开关中实际上需要@JohnWu 进入另一种情况。不过,那是我使用过goto的唯一情况。
丹·里昂斯

Answers:


33

显然需要go-to语句是因为您为循环选择了不良的条件表达式

您声明自己希望外循环继续到i < 10,而最内循环继续到j > 0

但是实际上,这并不是您想要的,您只是没有告诉循环您要让他们评估的真实条件,而是想要使用break或goto来解决它。

如果您告诉循环您的真实意图是从开始的,则无需中断或转到。

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

请参阅我对其他答案的评论。原始代码打印i在外循环的末尾,因此,使增量短路对于使代码看起来100%等效非常重要。我并不是说您的答案有任何错误,我只是想指出,立即打破循环是代码的重要方面。
pswg 2014年

1
@pswg我i在OP的问题的外循环末尾看不到任何代码打印。
TulainsCórdova'14

OP在这里引用了我的问题中的代码,但省略了该部分。我没打败你,顺便说一句。对于正确选择回路条件,答案是完全正确的。
pswg 2014年

3
@pswg我猜如果您真的真的想证明goto的合理性,您可以解决一个需要解决的问题,另一方面,我已经专业编程了二十年了,没有一个实际的问题需要解决一。
图兰斯·科尔多瓦2014年

1
该代码的目的不是提供一个有效的用例goto(我敢肯定会有更好的用例),即使它产生了一个看起来像是在使用它的问题,但只是说明了break(n)语言的基本作用,支持它。
pswg 2014年

34

在这种情况下,您可以将代码重构为一个单独的例程,然后仅从return内部循环中进行重构。这样就不需要打破外部循环了:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

2
就是说,是的,您显示的内容是摆脱嵌套循环的不必要的复杂方法,但是不,有一种重构方法不会使goto更具吸引力。
罗杰

2
附带说明:在原始代码中,它i在外循环结束后打印。因此,要真正体现这i一点很重要,这里return true;return false;都应该是return i;
pswg 2014年

+1,正好要发布此答案,并认为这应该是公认的答案。我不敢相信接受的答案会被接受,因为该方法仅适用于嵌套循环算法的特定子集,并不一定会使代码更简洁。此答案中的方法通常有效。
本李

6

不,我认为没有goto必要。如果您这样写:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

&&!broken两个回路可以让你打破这两个循环的时候了i==j

对我而言,这种方法更可取。循环条件非常清楚:i<10 && !broken

可以在这里看到工作版本:http : //rextester.com/KFMH48414

码:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

输出:

当i == j == 1时循环中断
2
0

为什么甚至broken在第一个示例中都使用?只需在内部循环上使用break。另外,如果您绝对必须使用broken,为什么还要在循环之外声明它呢?刚宣布它里面i循环。至于while循环,请不要使用它。老实说,我认为它的可读性甚至低于 goto版本。
luiscubal 2014年

@luiscubal:使用了一个名为的变量,broken因为我不喜欢使用该break语句(在切换情况下除外,但仅此而已)。如果要在时中断所有循环,则在外部循环外部声明它i==j。通常,您broken只需要在将要中断的最外层循环之前声明就可以了。
FrustratedWithFormsDesigner 2014年

在您的更新中,i==2循环的结尾。成功条件还应该使增量短路(如果增量等于100%)break 2
pswg 2014年

2
我个人更喜欢broken = (j == i)if和(如果语言允许的话)将损坏的声明放在外部循环初始化中。尽管在这个特定示例中很难说出这些事情,但由于整个循环是无操作的(它不返回任何内容且没有副作用),并且如果执行了某些操作,则可能在if中执行该操作(尽管我仍然broken = (j ==i); if broken { something(); }更喜欢))
Martijn 2014年

@Martijn在我的原始代码中,它i在外循环结束后打印。换句话说,唯一真正重要的想法是何时确切地脱离外部循环。
pswg 2014年

3

简短的回答是“取决于”。我主张选择有关代码的可读性和可理解性。转到方法可能比调整条件更清晰,反之亦然。您可能还会考虑到最少惊奇原则,商店/项目中使用的准则以及代码库的一致性。例如,goto离开开关或进行错误处理是Linux内核代码库中的常见做法。还要注意,有些程序员可能在阅读您的代码时遇到问题(要么因为“ goto is evil”而提出,要么由于老师的想法而没有学到),其中一些甚至可能“判断您”。

一般来说,goto在相同功能中走得更远通常是可以接受的,尤其是在跳转不太长的情况下。您留下一个嵌套循环的用例是“不太邪恶” goto的已知示例之一。


2

在您的情况下,goto足以使它看起来很诱人,但并没有多大改进,因此值得违反在代码库中使用它们的规则。

想想您将来的审稿人:他/他将不得不考虑:“这个人是一个不好的编码员,还是实际上这是goto值得的情况?让我花5分钟的时间自行决定或研究互联网。” 当您不能完全使用goto时,为什么要对将来的编码器执行此操作?

通常,这种心态适用于所有“不良”代码,甚至可以对其进行定义:如果它现在可以工作,并且在这种情况下是好的,但是会努力验证其正确性,在这个时代,goto总是会这样做,然后就不这样做了。做吧。

话虽这么说,是的,您制作了其中一个较为棘手的案件。你可以:

  • 使用更复杂的控制结构(例如布尔标志)打破两个循环
  • 将内部循环放入自己的例程中(可能是理想的)
  • 将两个循环都放到例程中,然后返回而不是中断

对于我来说,很难创建一个双循环不太紧密的情况,以至于无论如何它都不应该成为自己的例行程序。

顺便说一下,我所见过的对此的最佳讨论是Steve McConnell的Code Complete。如果您喜欢批判性地考虑这些问题,我强烈建议您阅读,甚至涵盖所有内容,尽管它非常庞大。


1
阅读和理解代码是我们的工作程序员。除非这是代码库的本质,否则不只是简单的代码。由于理解的成本,在使用goto的通用性(而不是规则)方面存在(并且将永远是例外)。只要收益大于成本,就应该使用goto;与软件工程中所有编码决策一样。
Dietbuddha 2014年

1
@dietbuddha在违反公认的软件编码约定方面存在成本,应予以充分考虑。即使该控制结构可能非常适合某种情况,也要增加程序中使用的控制结构的多样性。破坏代码的静态分析是有代价的。如果这一切仍然值得,那我是谁呢。
djechlin 2014年

1

TLD; DR:没有具体说明,一般是

对于您使用的特定示例,出于许多其他原因指出的相同原因,我会拒绝。您可以轻松地将代码重构为等效的良好形式,从而无需使用goto

一般来说取缔打击goto的基础上的理解性质普遍性goto和使用它的成本。软件工程的一部分是制定实施决策。这些决定的合理性是通过权衡成本与收益之间的关系而实现的,只要收益大于成本,就可以实施。由于成本和收益的不同估值而引起争议。

关键是,总是存在a goto被证明是合理的例外,但是仅因某些原因而存在不同的估值。可惜的是,许多工程师只是出于故意的无知而将技术排除在外,因为他们是在互联网上阅读技术,而不是花时间去理解原因。


您可以在goto语句中推荐任何文章进一步说明费用吗?
Thys

-1

我认为这种情况并不能说明一个例外,因为可以这样写:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.