有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

为什么我应该在循环中使用foreach而不是for(inti=0;I<length;I++)?

在C#和Java中,似乎最酷的循环方式是使用foreach而不是C风格进行循环

有什么理由让我更喜欢这种风格而不是C风格吗

我对这两个案例特别感兴趣,但请尽可能多地解释你的观点

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

共 (6) 个答案

  1. # 1 楼答案

    对我来说,一个好处是不太容易犯像这样的错误

        for(int i = 0; i < maxi; i++) {
            for(int j = 0; j < maxj; i++) {
    ...
            }
        }
    

    更新: 这是错误发生的一种方式。我算了一笔钱

    int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    

    然后决定将其聚合更多。所以我用另一个循环来包装这个循环

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

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

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

    现在代码中至少有两个错误(如果我们搞乱了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;
    }
    

    而且更具可读性

  2. # 2 楼答案

    • foreach更简单,可读性更强

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

    • 并非所有集合都支持随机访问;迭代HashSet<T>Dictionary<TKey, TValue>.KeysCollection的唯一方法是foreach

    • foreach允许您在不使用额外临时变量的情况下迭代方法返回的集合:

      foreach(var thingy in SomeMethodCall(arguments)) { ... }
      
  3. # 3 楼答案

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

    1)它从底层容器类型中抽象出来。例如,这意味着您不必在更改容器时更改循环容器中所有项的代码——您指定的是“为容器中的每个项执行此操作”的目标,而不是的意思

    2)它消除了一次关闭错误的可能性

    就对列表中的每个项目执行操作而言,直观地说:

    for(Item item: lst)
    {
      op(item);
    }
    

    它完美地向读者表达了意图,而不是手动使用迭代器。搜索项目同上

  4. # 4 楼答案

    想象一下,你是一家餐厅的主厨,你正在为自助餐准备一份巨大的煎蛋。你把一盒一打的鸡蛋递给两个厨房工作人员,然后让他们开始吃鸡蛋

    第一种方法是设置一个碗,打开板条箱,依次从左到右抓住每一个鸡蛋——从上一排到下一排——将鸡蛋靠着碗的侧面打碎,然后将鸡蛋倒进碗中。最后,他的鸡蛋用完了。做得好的工作

    第二个人摆好碗,打开板条箱,然后冲出去拿一张纸和一支笔。他把数字0到11写在鸡蛋盒的隔间旁边,把数字0写在纸上。他看了看纸上的数字,找到标有0的隔间,取出鸡蛋并将其放入碗中。他又看了看纸上的0,想“0+1=1”,划掉0,把1写在纸上。他从1舱抓起鸡蛋,把它打碎。以此类推,直到数字12出现在纸上,他才知道(没有看!)没有鸡蛋了。做得好的工作

    你会认为第二个人的脑袋有点乱,对吧

    使用高级语言的目的是避免用计算机的术语来描述事物,并且能够用自己的术语来描述它们。语言的层次越高,这一点就越真实。增加循环中的计数器会分散您真正想做的事情:处理每个元素


    除此之外,通过增加计数器和索引,无法有效地处理链表类型的结构:“索引”意味着从头开始计数。在C语言中,我们可以通过使用循环“计数器”的指针并取消引用来处理我们自己创建的链表。我们可以在现代C++中(使用C语言)和java语言来使用这一点,但是这仍然存在间接性问题。p>


    最后,有些语言已经达到了相当高的水平,以至于写一个循环来“对列表中的每个项目执行操作”或“搜索列表中的项目”的想法是令人震惊的(就像厨师长不必告诉第一个厨房工作人员如何确保所有鸡蛋都破裂一样)。提供了设置循环结构的函数,您可以通过一个高阶函数,或者在搜索情况下通过一个比较值,告诉它们在循环中要做什么。(事实上,C++中可以做这些事情,尽管接口有些笨拙。)

  5. # 5 楼答案

    foreach将在所有场景中执行与for相同的操作[1],包括您描述的简单场景

    但是foreachfor相比,具有某些与性能无关的优势:

    1. 方便性。您不需要保留额外的局部i(这在生活中除了促进循环之外没有其他用途),您也不需要自己将当前值提取到变量中;循环构造已经解决了这个问题
    2. 一致性。使用foreach,您可以同样轻松地迭代不是数组的序列。如果您想使用for在非数组顺序的序列(例如映射/字典)上循环,那么您必须以稍微不同的方式编写代码foreach在它涵盖的所有情况下都是相同的
    3. 安全性。权力越大,责任越大。如果一开始不需要循环变量,为什么会出现与递增循环变量相关的错误

    因此,正如我们所看到的,foreach大多数情况下使用“更好”

    这就是说,如果您出于其他目的需要i的值,或者如果您正在处理一个您知道是数组的数据结构(并且它是数组有一个实际的特定原因),那么更深入的金属for提供的增加的功能将是一条出路

    [1]“在所有场景中”实际上是指“集合易于迭代的所有场景”,这实际上是“大多数场景”(见下面的注释)。然而,我真的认为涉及不友好的迭代集合的迭代场景必须被设计

  6. # 6 楼答案

    如果你把C语言作为一种语言,你应该考虑一下LINQ,因为这是另一种逻辑循环方法。p>

    通过对列表中的每个项目执行操作您的意思是在列表中适当地修改它,还是简单地对项目执行一些操作(例如打印、累积、修改等)?我怀疑是后者,因为C#中的foreach不允许您修改正在循环的集合,或者至少不允许以方便的方式修改集合

    下面是两个简单的构造,首先使用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 ());
    }
    

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

    以下是foreach等价物:

    List<string> list = ...;
    List<string> uppercase = new List<string> ();
    foreach (name in list)
    {
        uppercase.Add (name.ToUpper ());
    }
    

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

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

    List<string> list = ...;
    List<string> uppercase = new List<string> ();
    uppercase.AddRange (list.Select (x => x.ToUpper ()));
    

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

    List<string> list = ...;
    List<string> uppercase = new List<string> (list.Select (x => x.ToUpper ()));
    

    或者使用ToList函数执行相同操作:

    List<string> list = ...;
    List<string> uppercase = list.Select (x => x.ToUpper ()).ToList ();
    

    或者仍然与类型推断相同:

    List<string> list = ...;
    var uppercase = list.Select (x => x.ToUpper ()).ToList ();
    

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

    List<string> list = ...;
    var uppercase = list.Select (x => x.ToUpper ());
    

    或者是另一个带有类似C#SQL的fromselect关键字的,完全等效的:

    List<string> list = ...;
    var uppercase = from name in list
                    select name => name.ToUpper ();
    

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

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

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

    以下是简单的LINQ等价物:

    List<string> list = ...;
    string result = list.Where (x => x.Contains ("Pierre")).FirstOrDefault ();
    

    或使用查询语法:

    List<string> list = ...;
    
    var results = from name in list
                  where name.Contains ("Pierre")
                  select name;
    string result = results.FirstOrDefault ();
    

    results枚举只在按需时执行,这意味着实际上,当调用列表上的FirstOrDefault方法时,该列表将只迭代到满足条件为止

    我希望这能为{}或{}辩论带来更多的背景,至少在未来几年。网络世界