27

我只是好奇人们对这个话题的看法。假设我有一个对象数组,我想遍历它们以查看对象是否包含某些值,如果是,我想停止循环。哪个是更好的做法 - 带中断的 for 循环或条件循环?

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

带中断的 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++;
}
4

18 回答 18

32

简而言之,您应该选择最容易阅读和维护的版本。

在稍早的时候,我知道打破循环被认为是一个禁忌(与 goto 语句相提并论)。循环应该在循环条件下中断,而不是在其他任何地方。因此,while 循环将是要走的路。

(这可能是汇编的保留,其中循环基本上是一个代码块,最后有一个 go-to-the-beginning-if-true 跳转语句。块中的多个条件跳转语句使其非常难以调试; 因此,它们将被避免并在最后合二为一。)

我觉得这个想法今天似乎发生了一些变化,尤其是对于 foreach 循环和托管世界;现在真的是风格问题。Break-on-found for 循环可能已经为许多人所接受,当然除了一些纯粹主义者。请注意,我仍然会避免在 while 循环中使用 break,因为这会混淆循环条件并使其混乱。

如果您允许我使用 foreach 循环,我认为下面的代码比它的 while-loop 兄弟容易阅读:

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并将行更改为 readif(BarkTwiceIfFound) barkTwice();以使副作用清晰。)


作为记录,您也可以在 for 循环中不中断地进行标志检查,但我觉得这实际上会损害可读性,因为您不希望在 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();
    }
}
于 2009-02-27T17:18:45.883 回答
8

我一直不喜欢breaks在代码中使用...在这种情况下,它似乎无关紧要,但在更多涉及的循环中,它可能会让另一个阅读它的编码人员感到非常困惑。一般来说,它经常导致不理解循环如何终止,直到编码人员发现break循环深处的嵌套。通过指定检查循环的每次迭代的标志条件,它使这一点更加清晰。

这个问题类似于return在不容易被发现的方法主体中的语句(而不是设置retVal变量并在方法结束时返回)。用一个小方法,这似乎很好,但它越大,就越混乱。

这不是操作效率的事情,而是可维护性的事情。

询问你的同事在特定情况下什么是可读和可理解的……这才是真正重要的。

于 2009-02-27T17:09:00.260 回答
5

我会说这取决于。在这种情况下,带中断的循环对我来说似乎更清楚。

于 2009-02-27T17:08:44.957 回答
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 循环更具可读性。

于 2009-02-27T17:16:37.963 回答
4

两者之间存在概念上的差异。for循环用于迭代离散集,while循环用于基于条件重复语句。其他语言添加finally子句和循环结构,如foreachor untilfor它们的传统循环往往要少得多。

无论如何,我使用的规则是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非常荒谬。那你怎么能证明抛出异常是合理的呢?这只是一个非本地和非确定性的 goto!顺便说一句,这不是对异常处理的咆哮,只是一个观察。

于 2009-02-27T17:57:08.693 回答
3

最有意义的那个将是最能将这个想法传达给阅读代码的人的那个。首先记住代码的可读性,你通常会做出正确的选择。通常,除非你真的需要,否则你不想使用类似的东西break,因为如果经常这样做,甚至只是在一组深度嵌套的表达式中,它会使事情变得难以理解。 continue有时可以起到与中断相同的作用,然后循环将正常退出,而不是因为它被打破了。在这种情况下,我可以用几种不同的方式来写这个。

可能你想要的最好的事情是修改你的while循环:

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

这很清楚并且不使用breakor continue,因此您一眼就能看出您将始终由于while表达式中指定的条件之一而终止。

ETA:可能应该i < arrayLenwhile循环中,否则它第一次失败,除非输入值与目标值相同......

于 2009-02-27T17:12:22.763 回答
3

我看到两个循环都有中断,对吗?

反正:

  • 当循环开始之前有已知次数(最大次数)的迭代时,我会选择 FOR 循环。
  • 否则我会选择 WHILE。
  • 在 FOR 循环中,我自由地使用 BREAK。
  • 在 WHILE 循环中,我更喜欢使用复杂条件而不是 BREAK(如果可能的话)。
于 2009-02-27T17:14:10.480 回答
2

我会说break,更清楚(即使你写了为什么你跳出循环的评论)恕我直言while循环不清楚,我会去休息

于 2009-02-27T17:12:52.887 回答
2

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

您似乎在条件循环代码中犯了两个错误的方式可能有证据表明这一点!

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

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

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

于 2009-02-27T17:37:18.227 回答
2

问题有两个方面:

  • 做什么(例如:查找其中一项是否包含位置中的指定人员)
  • 怎么做(例如:使用索引、迭代等)

这两个示例都将两者混合在一起,很难从how中理解what。如果我们只能在代码中表达what部分,那将是最好的。这是一个使用规范模式执行此操作的示例(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;
    }
}
于 2009-02-27T20:07:59.127 回答
1

这在很大程度上取决于具体情况。但是在您的示例中,您想要遍历一个有界长度的数组,并且使用 for 循环可以很容易地做到这一点并防止跑到最后。在您的 while 循环示例中,您必须自己进行递增(如果您想使用continue语句跳到下一个循环,这可能会出现问题)并制作更复杂的条件表达式(顺便说一下,它有一个错误; 我想你的意思是&& i != arrayLen)。您只需要执行额外的代码来完成 for 循环帮助提供的效果。

当然,一些纯粹主义者会争辩说breakcontinue不应该使用,如果需要,您应该使用 if-else 和布尔变量,而不是继续或跳出循环。但我认为这会使循环看起来更难看,特别是如果它相对较短且通常像这个例子一样容易掌握。对于具有更长代码的循环,其中中断或继续很容易隐藏而不会引起注意,纯粹的方法可能更清楚,因为在这种情况下,循环已经很难掌握。但是您始终可以将其作为 for 循环的一部分执行,只需将其添加为条件的一部分。

更好的做法是测试绑定的数组i < arrayLen而不是完全相等,以防万一导致i跳过确切的值(我实际上在 Y2K 错误中看到了这种情况,更好的做法可以避免这种情况)。

于 2009-02-27T17:24:51.860 回答
1

我有 C++ 背景,所以我仍然有尝试“像编译器一样思考”的时候。While 循环往往会导致代码更紧凑,因此只有在您知道每次都要迭代数组中的每个元素时才考虑使用 for 循环。

编辑:我现在认为这是矫枉过正,如果你正在使用 .Net 或任何你不打算用几个紧密循环来弥补 VM 开销的东西。我确实认为记住某些做法的“原因”是一件好事。

于 2009-02-27T17:24:53.683 回答
1

我的理论是,有一个有用的编程抽象类似于“信噪比”,即“问题与工具的比率”——可以通过我花多少时间思考问题和它的解决方案,与我花时间思考如何使用该工具(在本例中为语言语法)相比。

通过这种方式,我尝试更频繁地使用更少的结构,因为我(以及希望跟随的人)可以更快、更准确地理解我的代码结构的本质。而且由于“for 循环”的变体很好地涵盖了可能使用其他循环的情况(没有失真),所以当它们可以互换时,我将它们作为首选。

并且很高兴在“for”循环顶部的一行中包含您需要了解(grokwise)的有关循环规则的所有内容。出于同样的原因,我也倾向于将“默认”开关放在测试的首位。

但一致性和清晰度是最重要的考虑因素。YMMV,当然。

于 2009-02-27T17:34:27.407 回答
1

我想两者实际上都不是很有趣。如果您追求的是可读性,您应该寻找更高级别的结构。

在 JS 中:

if(myArray.some(function(o) { o.name == "baxter" && o.location == "milwaukee" }))
  barkTwice();

或使用您自己的一些实用程序

if(myArray.containsMatch({name:"baxter",location:"milwaukee"})
  barkTwice();
于 2009-02-28T11:54:49.290 回答
1

将循环封装在自己的方法中,并在匹配条件成功时使用返回到结束处理。

一些示例 C# 代码:

class Program
{
   static bool IsBaxterInMilwaukee(IList<WhoAndWhere> peopleAndPlaces)
   {
      foreach (WhoAndWhere personAndPlace in peopleAndPlaces)
      {
         if (personAndPlace.Name == "Baxter" 
            && personAndPlace.Location == "Milwaukee")
         {
            return true;
         }
      }
      return false;
   }

   static void Main(string[] args)
   {
      List<WhoAndWhere> somePeopleAndPlaces = new List<WhoAndWhere>();
      somePeopleAndPlaces.Add(new WhoAndWhere("Fred", "Vancouver"));
      somePeopleAndPlaces.Add(new WhoAndWhere("Baxter", "Milwaukee"));
      somePeopleAndPlaces.Add(new WhoAndWhere("George", "London"));

      if (IsBaxterInMilwaukee(somePeopleAndPlaces))
      {
         // BarkTwice()
         Console.WriteLine("Bark twice");
      }
   }

   public class WhoAndWhere
   {
      public WhoAndWhere(string name, string location)
      {
         this.Name = name;
         this.Location = location;
      }

      public string Name { get; private set; }
      public string Location { get; private set; }
   }

}
于 2009-03-03T00:43:56.100 回答
0

我的总体立场是:

如果它有一个循环计数器,请使用 for() (就像 while while 循环一样)。

于 2009-02-27T22:15:54.640 回答
0

我投票while是因为休息会降低可理解性。

如果循环变得太长并且您插入您希望运行的代码但它没有运行,您可能没有意识到循环包含中断。

但我订阅了不要让我思考编码模型。

于 2009-02-27T22:22:59.193 回答
0

在 ES6 中,它已经变得非常简单,不需要使用 break 关键字,我们可以使用 find 函数,一旦条件满足,我们就可以返回 true。

let barkTwice = () => {
    console.log('bark twice');
}

let arr = [{
        location: "waukee",
        name: "ter"
    }, {
        location: "milwaukee",
        name: "baxter"
    },
    {
        location: "sample",
        name: "sampename"
    }
];

在这里,我们匹配条件,一旦条件匹配,我们就根据问题调用函数,然后我们返回 true。这样它就不会超越。

arr.find(item => {
    if (item.location == "milwaukee" && item.name == "baxter") {
        barkTwice();
        return true
    }
});
于 2019-04-24T12:36:02.043 回答