在for循环中使用break是一种不好的做法吗?

155

在for循环中使用break语句是否是不好的实践?

比如,我正在寻找数组中的一个值。在for循环内部进行比较,当找到该值时,使用break;退出循环。

这样做是不好的实践吗?我看到了替代方案:定义一个变量vFound,并在找到值时将其设为true,并在for语句的条件中检查vFound。但是为了这个目的而创建一个新变量是必要的吗?

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

P.S: MISRA编码指南建议避免使用break语句。


31
不要把 breakgoto 放在同一级别上 :) - BoltClock
2
额,我还没有把它们放在与goto相同的级别上...如果我没记错,MISRA规则指定了break。 - kiki
2
在循环中不使用break的唯一原因是当您仍然需要处理可能会改变您正在进行的操作结果的更多项时... - Younes
5
MISRA规则已经得到放宽:http://www.misra.org.uk/forum/viewtopic.php?f=75&t=298 - Johan Kotlinski
1
请勿重复:http://programmers.stackexchange.com/a/58253/72317 - Felipe
显示剩余5条评论
19个回答

154

不,break 是正确的解决方案。

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


3
同意。尤其是当你想在不止一个条件下退出循环时,用于退出循环的布尔值可能会变得非常令人困惑。 - Rontologist
2
这就是为什么我使用goto来跳出2级或更多级嵌套循环的原因 - 因为没有break(level) - Петър Петров
4
我宁愿把循环代码放在一个函数里,然后直接返回。这样可以避免使用goto,并将代码分成更小的块。 - Dave Branton

144

这里给出了很多答案,但我还没有看到提到以下内容:

在使用for循环中的breakcontinue时,大部分与其相关的“危险”都可以通过编写整洁、易读的循环来消除。如果你的循环体跨越多个屏幕长度并具有多个嵌套子块,那么你很容易忘记在break之后某些代码将不会被执行。然而,如果循环简短明了,那么break语句的作用应该是显而易见的。

如果一个循环变得太大,请在循环内部使用一个或多个命名良好的函数调用来代替。唯一真正需要避免这样做的原因是处理瓶颈。


12
很正确。当然,如果循环非常大和复杂,以至于难以看清内部发生的事情,那么无论有没有break,这都是一个问题。 - Jay
2
另一个问题是break和continue会导致重构时出现问题。这可能是一个不好的实践的迹象。当使用if语句时,代码的意图也更清晰。 - Ed Greaves
那些“命名良好的函数调用”可以是本地定义的lambda函数,而不是外部定义的私有函数,这些函数在其他地方不会被使用。 - DavidRR
我总是说:如果你的函数很小,那么就不存在所谓的早期返回。我想这也适用于循环和中断。 - Johan

55

在一些专业的代码中,你可以找到带有“break”语句的各种代码。如果需要,使用它是完全有意义的。在您的情况下,这个选项比为了跳出循环而创建一个单独的变量更好。


54

for 循环中使用 breakcontinue 是完全可以的。

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


是的。有一段时间我不喜欢这个想法并绕过了它,但很快我意识到这种“变通方法”经常会成为维护的噩梦。嗯,不是噩梦,而是更容易出现错误。 - MetalMikester

25
远非不良实践,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 语言中而不会产生歧义。 - supercat
类C语言 - nirvanaswap

18

通用规则:如果遵循一条规则需要你做出更加困难和难以阅读的事情,那么就打破这个规则。

在寻找某些东西的情况下,当你找到它并退出时,你会遇到区分已找到与未找到的问题。也就是说:

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

这也有助于使代码更加简洁。在主要的代码行中,“find”变成了一条单独的语句,而当条件比较复杂时,它们只需要编写一次。


2
正是我想说的。通常当我发现自己使用 break 时,我会尝试将代码重构为一个函数,这样我就可以使用 return 代替它。 - Rene Saarsoo
我完全同意你的观点,使用break并没有什么本质的问题。然而,这通常表明包含break的代码可能需要被提取到一个函数中,在函数中可以用return代替break。这应该被视为一种"代码异味"而不是错误。 - vascowhite
你的回答可能会促使一些人探讨使用多个返回语句的可行性。 - DavidRR
1
@DavidRR 我个人认为禁止多个返回值的规则是一个不好的想法。因为通常这意味着你必须设置标志和中断循环,然后在底部测试标志以决定返回什么值。如果你有一个非常长和复杂的函数,在中间加上一个返回可能会让读者感到困惑,因为他们可能没有意识到它的存在。但大多数情况下,我认为避免多个返回会导致比有多个返回更丑陋的代码。 - Jay

16

在使用break语句时并没有什么本质的问题,但是嵌套循环会让代码变得混乱。为了提高可读性,许多编程语言(至少Java支持)支持使用标签来进行break,这将极大地改善代码的可读性。

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)语句经常增加圈复杂度,这使得在所有情况下证明代码正确执行变得更加困难。

如果您正在考虑在迭代序列以获取特定项时使用break,则可能需要重新考虑用于保存数据的数据结构。使用类似Set或Map的东西可能会提供更好的结果。


4
对于圈复杂度加1。 - sp00m
1
.NET程序员可能希望探索使用LINQ作为复杂的“for”循环的潜在替代方案。 - DavidRR

15

break语句是完全可以使用的(顺便说一下,continue也是)。这取决于代码的可读性——只要您没有过于复杂的循环等,那么使用break语句就没问题。

它不像goto语句一样,它们并不在同一个级别。 :)


4
goto语句存在的问题在于它可以跳转到任何地方。而break和continue语句则可以保持代码的模块化。这个观点与每个函数都只有一个出口点的观点相同层次。 - Zachary Yates
1
我听说过有人争论说,为一个函数设置多个退出点实际上等同于使用 goto。;D - glenatron
2
@glenatron goto的问题不在于goto语句本身,而是在于引入goto跳转到的标签。当你读到一个goto语句时,控制流程是没有任何不确定性的。但是当你读到一个标签时,就会有很多关于控制流程的不确定性(有哪些goto语句会将控制权转移到这个标签?)。break语句不会引入标签,因此不会引入任何新的复杂性。 - Stephen C. Steel
1
"break"是一种特殊的goto语句,只能离开块。使用“goto”的历史问题是:(1)它可以被经常用于构建重叠的循环块,这是使用正确的控制结构所不可能的;(2)一个常见的做法是进行“if-then”构造,通常是“false”,将真实情况分支到代码中的一个无关位置,然后再分支回来。这在罕见情况下会导致两个分支,而在普通情况下则不需要,但这使得代码看起来很丑陋。 - supercat
IF语句实际上就是一个GOTO。 :-) - Jay
显示剩余3条评论

14

这取决于编程语言。虽然在这里您可能可以检查布尔变量:

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

当遍历一个数组时,这是不可能做到的:

for element in bigList: ...
无论如何,break会使两个代码更易读。

8
我同意其他人建议使用break。显而易见的问题是,为什么会有人推荐不使用呢?原因在于,当你使用break时,你会跳过块中剩余的代码和迭代。有时这会导致错误,例如:
  • 在块顶部获取的资源可能在底部释放(即使是在for循环内的块也是如此),但是当break语句引起“过早”的退出时,释放步骤可能被意外跳过(在“现代”C++中,“RAII”用于以可靠和异常安全的方式处理此问题:基本上,对象析构函数在任何作用域退出时都可以可靠地释放资源)

  • 有人可能更改for语句中的条件测试,而没有注意到存在其他非局部化的退出条件

  • ndim的回答指出,有些人可能会避免使用break来保持相对一致的循环运行时间,但您将break与使用布尔型的早期退出控制变量进行比较,而这种情况并不成立

偶尔会有人观察到此类错误,意识到可以通过这个“无break”规则来预防/减轻...事实上,还有一种名为“结构化编程”的“更安全”的编程策略,其中每个函数也应该具有单个入口和出口点(即没有goto,没有早期返回)。它可能会消除一些错误,但无疑会引入其他错误。他们为什么要这样做呢?

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

嗯,是的,但我也见过那些虔诚地试图避免使用 break 的人在代码中出现错误。他们确实避免了使用 break,但未能仔细思考替代 break 的条件。 - Tomáš Zato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接