在for循环中使用break是不好的做法吗?[关闭]


123

循环内使用break语句是否不好?for

说,我正在搜索数组中的值。在for循环内进行比较,并在找到值时break;退出for循环。

这是不好的做法吗?我已经看到了使用替代:定义一个变量vFound,当被发现的价值它设置为true,并检查vFoundfor语句条件。但是是否有必要为此目的创建一个新变量?

我问的是在普通的C或C ++ for循环的上下文中。

PS:MISRA编码准则建议不要使用中断。


28
不要放置break在同一个联盟的goto:)
BoltClock

2
呃,我没把他们和去...放在一起。如果我没记错的话,MISRA规则会指定休息时间。
kiki 2010年

1
我能想到的不要在循环内使用break的唯一原因是,当您仍然必须处理更多可能会改变您正在执行的结果的项目时
Younes 2010年


Answers:


122

这里有很多答案,但是我还没有看到这个问题:

如果编写整洁,易于理解的循环,则与使用breakcontinue在for循环中关联的大多数“危险” 都可以消除。如果循环的主体跨越了几个屏幕长度,并且具有多个嵌套的子块,是的,您很容易忘记在中断之后某些代码将不会执行。但是,如果循环很短并且很关键,则break语句的目的应该显而易见。

如果循环太大,请在循环内使用一个或多个命名良好的函数调用。避免这样做的唯一真实原因是处理瓶颈。


11
确实如此。当然,如果循环是如此之大和复杂,以至于很难看清内部正在发生什么,那么无论是否休息,这都是一个问题。
杰伊

1
另一个问题是中断和继续会导致重构问题。这可能表明这是一种不良做法。使用if语句时,代码的意图也更加清楚。
Ed Greaves 2015年

这些“命名函数调用”可以是本地定义的lambda函数,而不是不会在其他地方使用的外部定义的私有函数。
DavidRR 2015年

135

不,休息是正确的解决方案。

添加布尔变量使代码更难阅读,并增加了潜在的错误源。


2
同意 特别是如果您希望在多个条件下退出循环。离开循环的布尔值会变得非常混乱。
眼科医生

2
这就是为什么我用goto打出来的水平2+嵌套的循环-因为没有break(level)
ПетърПетров

3
我宁愿将循环代码放在函数中,然后返回。这避免了goto,并将您的代码分成较小的块。
戴夫·布兰顿

53

您可以在其中找到所有带有'break'语句的专业代码。必要时使用此选项非常合理。就您而言,仅出于退出循环的目的,此选项比创建单独的变量更好。


47

在循环中使用break和都很好。continuefor

它简化了代码并提高了可读性。


是的。有一段时间我不喜欢这个主意并对其进行编码,但是不久之后我才意识到...“解决方法”通常是一场维护噩梦。好吧,这不是一场噩梦,而是更容易出错。
MetalMikester,2010年

24

Python(和其他语言?)绝不是不好的实践,它扩展了for循环结构,因此只有在循环 执行时,它的一部分才会执行break

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

输出:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

等价的代码,没有break那么方便else

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')

2
噢,我很喜欢,有时也希望用C语言。它的语法不错,可以毫无歧义地适应将来的C语言。
超级猫

“类C语言”:P
nirvanaswap

15

使用break语句本质上并没有错,但是嵌套循环可能会造成混乱。为了提高可读性,许多语言(至少Java都支持)打破了标签,这将大大提高可读性。

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

我要说的是,break(和return)语句通常会增加循环复杂性,这使得在所有情况下都很难证明代码在做正确的事情。

如果在考虑迭代某个特定项目的序列时考虑使用中断,则可能需要重新考虑用于保存数据的数据结构。使用诸如Set或Map之类的东西可能会提供更好的结果。


4
+1为圈复杂度。
sp00m 2013年

.NET程序员可能想探索使用LINQ作为复杂循环的潜在替代方法for
DavidRR 2015年

14

break是一个完全可以接受的语句(因此继续)和btw)。这全都与代码的可读性有关—只要您没有过于复杂的循环,就可以了。

好像他们和goto是同一个联赛。:)


我已经看到它认为,一个break语句有效的一个跳转
glenatron 2010年

3
goto的问题在于您可以将其指向任何地方。中断和继续都保留了代码的模块化。该参数与每个函数具有单个退出点的级别相同。
Zachary Yates 2010年

1
我听说它认为,有多个出口点的功能有效的一个跳转。; D
glenatron

2
@glenatron goto的问题不是goto语句,而是goto跳转到的标签的引入,这就是问题所在。读完goto语句后,对于控制流没有任何不确定性。阅读标签时,控制流程存在很多不确定性(将控制权转移到该标签的所有要素都在哪里?)。break语句不会引入标签,因此不会引入任何新的复杂性。
斯蒂芬·斯蒂尔·斯蒂尔

1
“中断”是一种特殊的goto,只能离开障碍物。“ goto”的历史问题是:(1)它可能并且经常被用来以适当的控制结构不可能的方式构造重叠的循环块;(2)进行“ if-then”构造(通常为“ false”)的常见方法是,将true case分支到代码中不相关的位置,然后分支回去。在极少数情况下,这将花费两个分支,在普通情况下将花费零,但是这使代码看起来很丑陋。
supercat

14

一般规则:如果遵循一条规则要求您做一些更尴尬且难以阅读的操作,那么请打破规则,然后再打破规则。

在循环进行直到找到某物的情况下,遇到了将发现时发现与未发现时区分开的问题。那是:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

好的,您可以设置一个标志,或将“找到的”值初始化为null。但

这就是为什么我通常倾向于将搜索推入功能:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

这也有助于整理代码。在主行中,“查找”成为单个语句,并且当条件复杂时,它们仅被写入一次。


1
正是我想说的。通常,当我发现自己使用时break,我会尝试寻找某种方法来将代码重构为函数,以便可以return代替使用。
Rene Saarsoo 2010年

我100%同意您的观点,使用break并不是天生的错误。但是,它通常表明可能需要将其中所包含的代码提取到一个函数中,在该函数中,break将被return取代。应该将其视为“代码异味”,而不是彻底的错误。
vascowhite

您的答案可能会促使某些人探索使用多个return语句的可取性。
DavidRR 2015年

13

这取决于语言。虽然您可以在此处检查布尔变量:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

遍历数组时不可能这样做:

for element in bigList: ...

无论如何,break这将使这两个代码更具可读性。


7

在您的示例中,您不知道for循环的迭代次数。为什么不使用while循环来代替,它允许在开始时就不确定迭代次数?

因此,通常不必使用中断状态记忆,因为可以将循环更好地声明为while循环。


1
如果存在已知的最大迭代次数,则“ for”循环通常会很好。话虽这么说,在人们正在搜索一项并计划创建一个不存在的项目的情况下,while(1)循环有时会更好。在这种情况下,如果代码找到该项目,它将直接中断,如果到达数组的末尾,它将创建一个新项目,然后中断。
超级猫

2
在给定的示例中-在数组中搜索键,for循环是正确的惯用构造。您无需使用while循环即可遍历数组。您使用for循环。(或for-each循环(如果有))。您这样做是出于对其他程序员的礼貌,因为他们认识到“ for(int i = 0; i <ra.length; i ++){}”的构造立即为“遍历数组”,为此构造一个中断只是一个“如果可能就早点退出”的陈述。
克里斯·库德莫 Chris Cudmore)2010年

7

我同意建议使用的其他人的观点break。显而易见的必然问题是,为什么有人会建议呢?好吧...当您使用break时,您将跳过块中的其余代码以及其余的迭代。有时这会导致错误,例如:

  • 在块顶部获取的资源可能在底部释放(即使对于for循环内的块也是如此),但是当break语句引起“过早”退出时(在“现代”中),该释放步骤可能会被意外跳过C ++“ RAII”用于以可靠且异常安全的方式处理此问题:基本上,无论如何退出范围,对象析构函数都可靠地释放资源)

  • 有人可能会在for声明中更改条件测试,而不会注意到还有其他非本地化的退出条件

  • ndim的答案指出,有些人可能避免breaks来维持相对一致的循环运行时间,但是您正在将其break与布尔型早退出控制变量的使用进行比较,该变量不成立

人们时不时发现这种错误,就会意识到可以通过“不间断”规则来预防/缓解这些错误……的确,对于“更安全”的编程,有一个整体策略称为“结构化编程”,其中每个功能都应该具有也只有一个入口和出口点(即没有goto,没有提早返回)。它可以消除一些错误,但是无疑会引入其他错误。他们为什么这样做?

  • 他们有一个鼓励特殊风格的编程/代码的开发框架,并且他们有统计证据表明,在这种有限的框架中可以产生净收益,或者
  • 他们受到了此类框架中编程指南或经验的影响,或者
  • 他们只是独裁的白痴,或者
  • 以上+历史惯性中的任何一个(相关的理由是,比现代C ++更适用于C)。

好吧,是的,但是我再次看到那些虔诚地试图避免的人的代码错误break。的确避免了它,但是没有考虑替代中断的条件。
托马什Zato -恢复莫妮卡

5

用途完全正确break-正如其他人所指出的那样,它与NBA在同一个联赛中遥遥领先goto

尽管vFound在循环外检查是否在数组中找到该值时可能要使用该变量。同样从可维护性的角度来看,具有表示退出标准的公共标志可能很有用。


5

我对当前正在使用的代码库(40,000行JavaScript)进行了分析。

我发现其中只有22条break陈述

  • 内部switch语句使用了19个(总共只有3个switch语句!)。
  • for循环内部使用了2个-我立即将其分类为代码,将其重构为单独的函数并替换为return语句。
  • 至于最后的break内部while循环……我跑去git blame看看谁写了这个废话!

所以根据我的统计:如果break在之外使用switch,则是代码异味。

我还搜索continue语句。一无所获。



4

在嵌入式世界中,有很多使用以下构造的代码:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

这是一个非常普通的示例,很多事情正在幕后发生,尤其是中断。不要将其用作样板代码,我只是想举例说明。

我个人认为,只要采取适当的措施防止无限期地保留在循环中,以这种方式编写循环就没有错。


1
您能详细说明一下吗?在我看来,循环绝对不执行任何操作……除非continue应用程序逻辑中没有语句。在那儿?
Rene Saarsoo 2010年

1
因为您已经受到无限循环的束缚,所以无需继续语句。您基本上执行了达到目标所需的必要逻辑,如果在n次尝试中未达到该目标或超时条件过期,则可以通过break结构退出循环并采取纠正措施或将错误告知通知调用代码。我看到这种类型的代码经常在通过公共总线(RS-485等)进行通信的uC中进行,您基本上是试图抓住线路并进行通信而不会发生冲突,如果发生冲突,则等待由随机数确定的持续时间,然后尝试再次。
Nate

1
相反,如果一切顺利,则在满足条件后也可以使用break语句退出。在这种情况下,执行循环之后的代码,每个人都很高兴。
Nate

“如果(call_some_bool_returning_function())返回TRUE;否则返回FALSE;” 可能只是“ return call_some_bool_returning_function()”
弗兰克斯特

3

取决于您的用例。在某些应用程序中,for循环的运行时间需要保持恒定(例如,满足某些时序约束,或使数据内部免受基于时序的攻击)。

在这种情况下,设置标志并仅在所有操作之后检查标志值才有意义。 for循环迭代实际运行。当然,所有的for循环迭代都需要运行仍需要大约相同时间的代码。

如果你不关心运行时间...使用break;continue;使代码更易于阅读。


1
如果时间安排很重要,那么最好让睡眠或节省系统资源比让程序执行已知的无用操作。
Bgs

2

根据我公司在C dev中使用的MISRA 98规则,不得使用break语句...

编辑:MISRA '04中允许中断


2
正是为什么我问这个问题!我没有很好的理由将这样的规则作为编码指南。而且,正如这里其他人所说,休息;看起来更好。
kiki 2010年

1
我不知道为什么要禁止它(比我想的要聪明的人...),但是中断(和多次返回)对于可读性更好(在我看来,但我的观点不是公司的规则... )
伯努瓦

1
呃,MISRA规则确实允许使用break语句:misra.org.uk/forum/viewtopic.php?
f=75&t

我们使用了MISRA 98规则...正是MISRA 2004更改了规则
Benoit

0

当然,break;是停止for循环或foreach循环的解决方案。我在foreach和for循环中的php中使用了它,发现可以正常工作。


0

我不同意!

为什么您会忽略for循环的内置功能来制作自己的功能?您无需重新发明轮子。

我认为这样将检查放在for循环的顶部更有意义

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

或者如果您需要先处理该行

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

这样,您可以编写一个函数来执行所有操作并编写更简洁的代码。

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

或者,如果您的情况很复杂,您也可以将该代码移出!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

在我看来,到处都是断断续续的“专业守则”,听起来并不像是专业守则。听起来像是惰性编码;)


4
惰性编码IS专业代码:)
杰森

仅在维持屁股不是痛苦的时候:)
Biff MaGriff

2
我倾向于练习“ GTFO”。也就是说,如果我可以干净地退出结构,则应尽快退出,并尽可能接近指定退出的条件。
克里斯·库德莫

那也行得通。我认为我想传达的观点是,如果您使用的是for循环,则使用其内置功能使您的代码可读性和模块化无害。如果您绝对需要在循环内使用break语句,我会坐下来思考为什么需要这种级别的复杂性。
Biff MaGriff

1
哈哈。考虑一下这样一个事实,即大多数劝阻的break人都认为这是为了代码可读性。现在,在疯狂的5英里长的条件再看看上面的循环...
托马什Zato -恢复莫妮卡
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.