我为什么要在循环中使用foreach而不是for(int i = 0; i <length; i ++)?

Med*_*Man 45 .net c# java foreach loops

在C#和Java中循环的酷炫方式似乎是使用foreach而不是C style for循环.

我有理由比C风格更喜欢这种风格吗?

我对这两种情况特别感兴趣,但请解决您需要解决的问题.

  • 我希望对列表中的每个项目执行操作.
  • 我正在搜索列表中的项目,并希望在找到该项目时退出.

Kar*_*tel 78

想象一下,你是一家餐厅的主厨,而且你们都准备了一个巨大的煎蛋卷来自助餐.你把一打十几个鸡蛋的纸盒交给两个厨房工作人员,然后告诉他们要开裂.

第一个设置一个碗,打开箱子,依次抓住每个鸡蛋 - 从左到右穿过顶行,然后是底行 - 将它打开碗的侧面,然后将其倒入碗中.最终他没蛋了.干得好.

第二个设置一个碗,打开箱子,然后破灭,得到一张纸和一支笔.他将数字0到11写在鸡蛋盒的隔间旁边,数字0写在纸上.他看着纸上的数字,找到标记为0的隔间,取出鸡蛋并将其打入碗中.他再次看着纸上的0,认为"0 + 1 = 1",越过0并在纸上写1.他从隔间1抓起鸡蛋并将其弄碎.等等,直到12号纸上,他知道(不看!)没有更多的鸡蛋.干得好.

你认为第二个人头脑中有点乱,对吧?

使用高级语言工作的重点是避免用计算机的术语描述事物,并能够用自己的术语描述它们.语言越高,这就越真实.在循环中递增计数器会分散您真正想要做的事情:处理每个元素.


除此之外,链接列表类型结构不能通过递增计数器和索引来有效处理:"索引"意味着从头开始计数.在C中,我们可以通过使用循环"计数器"的指针并解除引用来处理我们自己制作的链表.我们可以使用"迭代器"在现代C++(以及在C#和Java中)中执行此操作,但这仍然存在间接性问题.


最后,一些语言足够高,以至于实际编写循环以"对列表中的每个项目执行操作"或"搜索列表中的项目"的想法令人震惊(与主厨相同)不应该告诉第一个厨房工作人员如何确保所有的鸡蛋都破裂了).提供了设置该循环结构的函数,并告诉它们 - 通过高阶函数,或者在搜索案例中可能是比较值 - 在循环中做什么.(事实上​​,你可以用C++做这些事情,虽然界面有些笨拙.)

  • 我一直在互联网上的其他地方使用它一段时间.:)我想我可以补充一点,如果他决定寻找"蛋#12",那么第二个人可能会有惊恐发作,然后意识到纸箱里没有这样的插槽.;) (4认同)
  • +1幽默:-).@Karl Knechtel我想知道EggIndexOutOfBoundsException和必要的try catch--)的类比是什么样的. (4认同)
  • 当然,抛出异常是一种惊恐发作.有些人比其他人更加可预测和明确地恐慌:)我认为在这种情况下,捕捉这样的例外,相当于主厨之前已经看过这个并且有快速拨号的治疗师,而不愿意教家伙如何"正常"(或只是解雇他). (3认同)

Stu*_*etz 71

我能想到的两个主要原因是:

1)它抽象远离底层容器类型.这意味着,例如,当您更改容器时,您不必更改循环容器中所有项目的代码 - 您指定的目标是"对容器中的每个项目执行此操作",不是手段.

2)它消除了逐个错误的可能性.

在对列表中的每个项目执行操作方面,只需说:

for(Item item: lst)
{
  op(item);
}
Run Code Online (Sandbox Code Playgroud)

它完全向读者表达了意图,而不是手动处理迭代器.同样用于搜索物品.

  • +1对于逐个错误.祝贺你的第二个好答案徽章. (8认同)

SLa*_*aks 37

  • foreach 更简单,更易读

  • 对于像链表这样的结构,它可以更有效

  • 并非所有馆藏都支持随机访问; 迭代a HashSet<T>或a 的唯一方法Dictionary<TKey, TValue>.KeysCollectionforeach.

  • foreach 允许您迭代方法返回的集合,而无需额外的临时变量:

    foreach(var thingy in SomeMethodCall(arguments)) { ... }
    
    Run Code Online (Sandbox Code Playgroud)

  • +1 - 用于指出'foreach'使用*iterator*而不是索引获取. (4认同)
  • @bitxwise:不; `thingy`对应于`i`.一个`for`循环需要_2_临时.(澄清) (2认同)
  • 我看到你添加了"额外",这更有意义.无论这个东西对应于i,两者都是临时变量.您的原始答案仅指定foreach不需要临时变量(没有"额外").甚至,你仍然可以引用myCollection [i],减轻了对额外临时变量的语法需求(不是我推荐它).无论哪种方式,我们都不要进入关于语义的精英讨论.干杯. (2认同)

pet*_*ust 19

对我来说,一个好处就是不容易犯错误等

    for(int i = 0; i < maxi; i++) {
        for(int j = 0; j < maxj; i++) {
...
        }
    }
Run Code Online (Sandbox Code Playgroud)

更新:这是bug发生的一种方式.我赚了一笔钱

int sum = 0;
for(int i = 0; i < maxi; i++) {
    sum += a[i];
}
Run Code Online (Sandbox Code Playgroud)

然后决定更多地聚合它.所以我将循环包装在另一个循环中.

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    total += sum;
}
Run Code Online (Sandbox Code Playgroud)

当然,编译失败,所以我们手动编辑

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int j = 0; j < maxj; i++) {
        sum += a[i];
    }
    total += sum;
}
Run Code Online (Sandbox Code Playgroud)

现在代码中至少有两个错误(如果我们混淆了maxi和maxj,则更多错误)只会被运行时错误检测到.如果你不编写测试......而且这是一段罕见的代码 - 这会让某些人陷入困境 - 非常糟糕.

这就是为什么将内循环提取到方法中是个好主意:

int total = 0;
for(int i = 0; i < maxi; i++) {
    total += totalTime(maxj);
}

private int totalTime(int maxi) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

而且它更具可读性.


Jon*_*Jon 16

foreachfor在所有场景[1]中执行与a相同的操作,包括您所描述的直截了当的场景[1].

但是,在以下foreach方面具有某些与性能无关的优势for:

  1. 方便.你不需要保留一个额外的本地i(除了促进循环之外没有任何其他目的),你不需要自己将当前值提取到变量中; 循环结构已经处理好了.
  2. 一致性.使用foreach,您可以轻松地迭代不是数组的序列.如果你想用来for遍历非数组有序序列(例如地图/字典),那么你必须以不同的方式编写代码.foreach它涵盖的所有情况都是一样的.
  3. 安全.拥有权利的同时也被赋予了重大的责任.如果您不首先需要增加循环变量,为什么要提供与增加循环变量相关的错误的机会?

正如我们所看到的,foreach大多数情况下使用"更好" .

也就是说,如果你需要i用于其他目的的值,或者你正在处理一个你知道的数组结构(并且它有一个实际的特定原因,它是一个数组),那么增加的功能就越多金属for报价将是您的最佳选择.

[1]"在所有场景中"实际上意味着"集合对迭代友好的所有场景",这实际上是"大多数场景"(参见下面的评论).不过,我真的认为必须设计一个涉及迭代不友好集合的迭代场景.


Pie*_*aud 10

如果您将C#作为一种语言,那么您也应该考虑使用LINQ,因为这是执行循环的另一种合理方式.

通过对列表中的每个项目执行操作,您的意思是在列表中将其修改,或者只是对项目执行某些操作(例如,打印,累积,修改等)?我怀疑它是后者,因为foreach在C#中不允许你修改你正在循环的集合,或者至少不方便...

这里有两个简单的构造,首先使用 for然后使用foreach,它们访问列表中的所有字符串并将它们转换为大写字符串:

List<string> list = ...;
List<string> uppercase = new List<string> ();
for (int i = 0; i < list.Count; i++)
{
    string name = list[i];
    uppercase.Add (name.ToUpper ());
}
Run Code Online (Sandbox Code Playgroud)

(请注意,在.NET 中使用结束条件i < list.Count而不是i < length一些预计算机length常量被认为是一种很好的做法,因为编译器无论如何都必须list[i]在循环中调用上限;如果我的理解是正确的,编译器是能够在某些情况下优化掉它通常会做的上限检查).

这是foreach等价的:

List<string> list = ...;
List<string> uppercase = new List<string> ();
foreach (name in list)
{
    uppercase.Add (name.ToUpper ());
}
Run Code Online (Sandbox Code Playgroud)

注意:基本上,foreach构造可以遍历任何IEnumerableIEnumerable<T>C#,而不仅仅是数组或列表.因此,集合中的元素数量可能事先不知道,或者甚至可能是无限的(在这种情况下,您肯定必须在循环中包含一些终止条件,否则它将不会退出).

这里有一些我能想到的等价解决方案,用C#LINQ表示(并且引入了lambda表达式的概念,基本上是一个内联函数,在下面的例子中使用x并返回x.ToUpper ()):

List<string> list = ...;
List<string> uppercase = new List<string> ();
uppercase.AddRange (list.Select (x => x.ToUpper ()));
Run Code Online (Sandbox Code Playgroud)

或者使用uppercase由其构造函数填充的列表:

List<string> list = ...;
List<string> uppercase = new List<string> (list.Select (x => x.ToUpper ()));
Run Code Online (Sandbox Code Playgroud)

或者使用相同的ToList功能:

List<string> list = ...;
List<string> uppercase = list.Select (x => x.ToUpper ()).ToList ();
Run Code Online (Sandbox Code Playgroud)

或者类型推断仍然相同:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ()).ToList ();
Run Code Online (Sandbox Code Playgroud)

或者如果你不介意将结果作为IEnumerable<string>(一个可枚举的字符串集合),你可以删除ToList:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ());
Run Code Online (Sandbox Code Playgroud)

或者可能是另一个具有C#SQL-like fromselect关键字的,完全等效:

List<string> list = ...;
var uppercase = from name in list
                select name => name.ToUpper ();
Run Code Online (Sandbox Code Playgroud)

LINQ非常富有表现力,我经常觉得代码比普通循环更具可读性.

您可以使用LINQ非常方便地实现第二个问题,即搜索列表中的项目,并希望在找到该项目时退出.这是一个foreach循环的例子:

List<string> list = ...;
string result = null;
foreach (name in list)
{
    if (name.Contains ("Pierre"))
    {
        result = name;
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是简单的LINQ等价物:

List<string> list = ...;
string result = list.Where (x => x.Contains ("Pierre")).FirstOrDefault ();
Run Code Online (Sandbox Code Playgroud)

或者使用查询语法:

List<string> list = ...;

var results = from name in list
              where name.Contains ("Pierre")
              select name;
string result = results.FirstOrDefault ();
Run Code Online (Sandbox Code Playgroud)

results枚举只执行需求,这意味着有效,该列表将只重复直到满足条件,调用时,FirstOrDefault方法就可以了.

我希望这会带来更多的背景forforeach争论,至少在.NET世界中.


tpd*_*pdi 6

正如Stuart Golodetz回答的那样,这是一种抽象.

如果你只是i用作索引,而不是i像其他目的那样使用值

   String[] lines = getLines();
   for( int i = 0 ; i < 10 ; ++i ) {
      System.out.println( "line " + i + lines[i] ) ;
   }
Run Code Online (Sandbox Code Playgroud)

那么就没有必要知道当前的价值i,并且只能导致错误的可能性:

   Line[] pages = getPages();
   for( int i = 0 ; i < 10 ; ++i ) {
        for( int j = 0 ; j < 10 ; ++i ) 
             System.out.println( "page " + i + "line " + j + page[i].getLines()[j];
   }
Run Code Online (Sandbox Code Playgroud)

正如Andrew Koenig所说,"抽象是有选择性的无知"; 如果您不需要知道如何迭代某些集合的详细信息,那么找到一种方法来忽略这些细节,并且您将编写更强大的代码.