Kar*_*tel 78
想象一下,你是一家餐厅的主厨,而且你们都准备了一个巨大的煎蛋卷来自助餐.你把一打十几个鸡蛋的纸盒交给两个厨房工作人员,然后告诉他们要开裂.
第一个设置一个碗,打开箱子,依次抓住每个鸡蛋 - 从左到右穿过顶行,然后是底行 - 将它打开碗的侧面,然后将其倒入碗中.最终他没蛋了.干得好.
第二个设置一个碗,打开箱子,然后破灭,得到一张纸和一支笔.他将数字0到11写在鸡蛋盒的隔间旁边,数字0写在纸上.他看着纸上的数字,找到标记为0的隔间,取出鸡蛋并将其打入碗中.他再次看着纸上的0,认为"0 + 1 = 1",越过0并在纸上写1.他从隔间1抓起鸡蛋并将其弄碎.等等,直到12号纸上,他知道(不看!)没有更多的鸡蛋.干得好.
你认为第二个人头脑中有点乱,对吧?
使用高级语言工作的重点是避免用计算机的术语描述事物,并能够用自己的术语描述它们.语言越高,这就越真实.在循环中递增计数器会分散您真正想要做的事情:处理每个元素.
除此之外,链接列表类型结构不能通过递增计数器和索引来有效处理:"索引"意味着从头开始计数.在C中,我们可以通过使用循环"计数器"的指针并解除引用来处理我们自己制作的链表.我们可以使用"迭代器"在现代C++(以及在C#和Java中)中执行此操作,但这仍然存在间接性问题.
最后,一些语言足够高,以至于实际编写循环以"对列表中的每个项目执行操作"或"搜索列表中的项目"的想法令人震惊(与主厨相同)不应该告诉第一个厨房工作人员如何确保所有的鸡蛋都破裂了).提供了设置该循环结构的函数,并告诉它们 - 通过高阶函数,或者在搜索案例中可能是比较值 - 在循环中做什么.(事实上,你可以用C++做这些事情,虽然界面有些笨拙.)
Stu*_*etz 71
我能想到的两个主要原因是:
1)它抽象远离底层容器类型.这意味着,例如,当您更改容器时,您不必更改循环容器中所有项目的代码 - 您指定的目标是"对容器中的每个项目执行此操作",不是手段.
2)它消除了逐个错误的可能性.
在对列表中的每个项目执行操作方面,只需说:
for(Item item: lst)
{
op(item);
}
Run Code Online (Sandbox Code Playgroud)
它完全向读者表达了意图,而不是手动处理迭代器.同样用于搜索物品.
SLa*_*aks 37
foreach
更简单,更易读
对于像链表这样的结构,它可以更有效
并非所有馆藏都支持随机访问; 迭代a HashSet<T>
或a 的唯一方法Dictionary<TKey, TValue>.KeysCollection
是foreach
.
foreach
允许您迭代方法返回的集合,而无需额外的临时变量:
foreach(var thingy in SomeMethodCall(arguments)) { ... }
Run Code Online (Sandbox Code Playgroud)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
foreach
将for
在所有场景[1]中执行与a相同的操作,包括您所描述的直截了当的场景[1].
但是,在以下foreach
方面具有某些与性能无关的优势for
:
i
(除了促进循环之外没有任何其他目的),你不需要自己将当前值提取到变量中; 循环结构已经处理好了.foreach
,您可以轻松地迭代不是数组的序列.如果你想用来for
遍历非数组有序序列(例如地图/字典),那么你必须以不同的方式编写代码.foreach
它涵盖的所有情况都是一样的.正如我们所看到的,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
构造可以遍历任何IEnumerable
或IEnumerable<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 from
和select
关键字的,完全等效:
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
方法就可以了.
我希望这会带来更多的背景for
或foreach
争论,至少在.NET世界中.
正如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所说,"抽象是有选择性的无知"; 如果您不需要知道如何迭代某些集合的详细信息,那么找到一种方法来忽略这些细节,并且您将编写更强大的代码.
归档时间: |
|
查看次数: |
30204 次 |
最近记录: |