使用break的for循环和条件循环哪个更好?

30

我只是好奇大家对这个话题的想法。假设我有一个对象数组,我想循环遍历它们以查看对象是否包含特定值,并且如果是,我希望停止循环。哪种做法更好——使用带有break的for循环还是条件循环?

我提供的示例伪代码仅供参考(它也是ActionScript,因为那是我最近的主要语言)。另外,我不是在寻找有关语法的最佳实践想法。

带有break的for循环:

var i:int;

var isBaxterInMilwaukee:Boolean;    

for (i = 0; i < arrayLen; i++)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();

        break;
    }
}

条件循环:

var i:int;

var isBaxterInMilwaukee:Boolean;    

while (!isBaxterInMilwaukee && i < arrayLen)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();
    }

    i++;
}

我从未使用过ActionScript,但while循环条件中的“i == arrayLen”部分似乎有误。应该是“i < arrayLen”或其他什么吧? - unwind
@unwind:是的,看起来你是对的,但那已经不是重点了 :) - dirkgently
是的,在条件语句中应该使用&&而不是||。我想他可能本来想用do-until循环而不是while循环?不过我们大概能理解他的意思... - lc.
18个回答

32
简而言之,您应该选择最容易阅读和维护的版本。 在稍早的时候,我知道跳出循环被认为是不可取的(与goto语句一样)。循环应该在循环条件上中断,而不是其他地方。因此,使用while循环会是更好的选择。
(这可能是由汇编语言带来的影响,其中循环基本上是一块代码,它在结尾处有一个真跳转语句返回到开头。块中的多个条件跳转语句使得调试变得非常困难;因此应避免在循环体中使用,而将其组合到末尾。)
我觉得这种想法似乎今天已经有所改变了,尤其是在foreach循环和托管世界中;这现在实际上只是一种风格问题。使用break-on-found的for循环现在可能已被许多人接受,当然还有一些纯粹主义者不接受。请注意,我仍然会避免在while循环中使用break,因为这可能会混淆循环条件并使其变得混乱。
如果您允许我使用foreach循环,我认为下面的代码比while循环更容易阅读:
bool isBaxterInMilwaukee;    

foreach (var item in myArray)
{
    if (item.name == "baxter" && item.location == "milwaukee")
    {
        isBaxterInMilwaukee = true;    
        barkTwice();
        break;
    }
}
然而,随着逻辑复杂度的增加,你可能需要考虑在break语句附近添加一个显著的注释,以免它被埋没和难以找到。
可以说,整个代码块都应该重构为自己的函数,不要在找到目标后使用break,而是实际上使用return返回结果(可以使用 for 循环版本)。
bool isBaxterInMilwaukee(Array myArray)
{      
    foreach (var item in myArray)
    {
        if (item.name == "baxter" && item.location == "milwaukee")
        {
            barkTwice();
            return true;
        }
    }
    return false;
}

正如Esko Luontola指出的那样,最好将对barkTwice()的调用移到此函数之外,因为该副作用不明显,与在每种情况下找到Baxter无关。(或者添加一个布尔参数BarkTwiceIfFound并更改该行以读取if(BarkTwiceIfFound) barkTwice(); 以使副作用清晰可见。)


顺便说一句,在for循环中也可以进行标志检查而不使用break,但我认为这实际上会影响可读性,因为您不希望在for循环定义中多出一个条件:

var i:int;

var isBaxterInMilwaukee:Boolean;    

for (i = 0; !isBaxterInMilwaukee && i < arrayLen; i++)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;    
        barkTwice();
    }
}

你也可以使用 while 循环模拟自增机制。我不喜欢这种方法,原因有几个:你必须将 i 初始化为真实起始值减一,而且根据编译器如何短路循环条件逻辑,离开循环时 i 的值可能会有所不同。然而,这是可能的,对于某些人来说,这可以提高可读性:

var i:int = -1;

var isBaxterInMilwaukee:Boolean;    

while (!isBaxterInMilwaukee && ++i < arrayLen)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;
        barkTwice();
    }
}

将代码重构为自己的函数是最清晰的。另外,将barkTwice()移出isBaxterInMilwaukee()也是一个好主意,因为函数的名称并没有以任何方式指示狗叫两声的副作用。 - Esko Luontola
例如:如果(isBaxterInMilwaukee(myArray)){barkTwice();} - Esko Luontola
完全同意。我不想让我的解释变得更长,但这确实值得一提。已编辑。 - lc.
重构成自己的函数比任何其他替代方案都要好得多。 - mqp

9
我一直不喜欢在代码中使用“breaks”... 在某些情况下似乎并不重要,但在更复杂的循环中,这可能会让另一个阅读代码的程序员感到非常困惑。通常情况下,这会导致程序员在发现嵌套的深层“break”之前无法理解循环如何终止。通过指定每次迭代循环都会检查的标志条件,可以使此问题变得更加清晰。
这个问题与在方法体中深处有“return”语句类似,它们不容易被发现(而不是设置一个“retVal”变量并在方法的结尾返回)。对于小型方法来说,这似乎还好,但它变得越大,就会变得越令人困惑。
这不是操作效率的问题,而是可维护性的问题。
询问你的同事,在特定情况下什么样的代码可读性和可理解性最强... 这才是真正重要的。

我喜欢这个想法:无论循环的类型如何,都要检查标志。 - Jeremy L
使用标志(flag)是好的,但在较大的循环中才有益,在短循环(几行代码)中容易被忽略,我更喜欢使用break。 - Jiri
在循环中使用 break/return 是没有问题的。 - codenamezero

6

我认为这取决于情况。在这种情况下,使用带break的循环似乎更清晰明了。


4

在for循环中,您还可以通过将早期退出条件放在for循环声明中来提前退出。因此,对于您的示例,您可以这样做:

var i:int;

var isBaxterInMilwaukee:Boolean;    

isBaxterInMilwaukee = false;

for (i = 0; i < arrayLen && !isBaxterInMilwaukee; i++)
{
    if (myArray[i]["name"] == "baxter"
        && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();
    }
}

这样你就不需要休息了,而且它比while循环更易读。


4
两者之间存在概念上的差异。for循环用于迭代离散集合,而while循环是基于条件重复语句的。其他编程语言添加了finally子句和像foreach或until这样的循环结构。它们往往有相对较少的传统for循环。
无论如何,我使用的规则是for循环用于迭代,而while循环用于重复。如果你看到像下面这样的内容:
while (counter <= end) {
   // do really cool stuff
   ++counter;
}

如果你正在迭代,那么最好使用 for 循环。然而,像这样的循环:

for (int tryCount=0; tryCount<2; ++tryCount) {
    if (someOperation() == SUCCESS) {
       break;
    }
}

应该写成while循环,因为它们实际上是在条件为真之前一直重复执行某些操作。

不使用break的想法,因为它和goto一样邪恶,这是相当荒谬的。那你如何证明抛出异常呢?那只是一种非本地且非确定性的跳转!顺便说一下,这并不是反对异常处理,只是一种观察。


3
最有意义的做法是将代码传达给阅读代码的人员,并使其易于理解。请记住,代码可读性第一,您通常会做出正确的选择。通常情况下,除非确实需要,否则不要使用类似“break”的语句,因为如果经常这样做甚至在嵌套的表达式中进行操作,会使得代码难以跟踪。有时,“continue”可以像“break”一样起到相同的作用,而循环将正常退出而不是被打破。在这种情况下,有几种不同的编写方式。
也许你在这里想要的最好的东西是修改您的while循环:
while(!isBaxterInMilwaukee || i < arrayLen) {
  if(myArray[i]["name"] == "baxter" && myArray[i]["location"] == "milwaukee") {
    isBaxterInMilwaukee == true;
    barkTwice()
  } else {
    i++;
  }
}

这段代码很清晰,没有使用 break 或者 continue,因此你可以一眼看出你总是会在 while 表达式中指定的条件之一终止。

预计时间到达: 在 while 循环中应该是 i < arrayLen,否则第一次循环将失败,除非输入值与目标值相同...


基本上,我不想重置布尔值。这就是问题的目的。 - Eric Belair

3

我看到两个循环中都有break,这正确吗?

无论如何:

  • 如果在循环开始之前已知迭代次数(最大次数),我会选择FOR循环。
  • 否则,我会选择WHILE循环。
  • 在FOR循环中,我自由地使用BREAK。
  • 在WHILE循环中,我更喜欢使用复杂条件而不是BREAK(如果可能的话)。

2

这个问题有两个方面:

  • 要做什么(例如:查找是否有一个项目包含指定位置的人员)
  • 如何实现(例如:使用索引、迭代等)

这两个例子混杂了两个方面,很难从中理解要做什么如何实现。最好的方法是只在代码中表达要做什么部分。以下是使用规范模式(c# 3.5)实现此目的的示例。

// what we are looking for?
IsPersonInLocation condition = new IsPersonInLocation("baxter", "milwaukee");

// does the array contain what we are looking for?
bool found = myArray.Find(item => condition.IsSatifiedBy(item));

// do something if the condition is satisfied
if (found) {
    barkTwice();
}

为了完整起见,这是该条件的类定义:

class IsPersonInLocation {
    public string Person { get; set; }
    public string Location { get; set; }
    public IsPersonInLocation(string person, string location) {
        this.Person = person;
        this.Location = location;
    }
    bool IsSatifiedBy(item) {
        return item["name"] == this.Person
            && item["location"] == this.Location;
    }
}

我赞同这个原则。但是对于如此简单的搜索来说,那些可怕的样板代码混乱无序,不切实际。 - John Nilsson
减少复杂性通常需要编写更多的代码。这可能看起来有些违反直觉,直到承认问题不在于代码总量,而在于您必须在某个特定时刻查看的代码量以理解它。 - Aleris
是的。但我的反应更多是对语言选择的反应。我发布了一个JavaScript版本的您的评论的替代版本,以说明差异。 - John Nilsson

2

我会选择使用break语句来退出循环,这样更加清晰易懂(即使你在注释中解释为什么要退出循环)。在我看来,使用while循环并不清晰,我宁愿选择使用break语句。


2
我肯定会选择使用for+break。'for' 是一个立即可识别的习语,表示“迭代序列”,比组合循环和停止条件更容易理解“迭代序列;如果找到值,则提前结束”。

你在条件循环代码中犯了两个错误,这可能证明了这一点!

  • while条件(!isBaxterInMilwaukee || i == arrayLen) - 你的意思是“(!(isBaxterInMilwaukee || i == arrayLen))”吗?

  • 如果使用终止循环变量,则不需要break语句。

就我个人而言,我发现简单的“break”比跟踪终止循环变量更容易阅读。


break语句在结束while循环的时候并非必要,但如果使用了break语句,则i++语句会执行,这样i就不再指向找到的位置(保留该位置可能会有帮助)。因此,避免使用额外的增量(通过break、if-else等)可能更有用。 - Rob Parker

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