while(list($key, $value) = each($array)) 和 foreach($array as $key => $value) 有什么不同?

41

最近我遇到了这个奇怪的问题:

while(list($key, $value) = each($array))

之前代码没有列出所有的数组值,在使用替换后...

foreach($array as $key => $value)

...完美地工作了。

而且,我现在很好奇..这两者之间有什么区别?


2
注意:foreach()适用于96%的所有用例。然而,有时您必须在迭代过程中更改数组本身,这就是each()的强大之处,因为foreach会在内部复制数组。请参见下面的答案。任何使用libxml的人都可以证明盲目地将foreach用于每种情况的危险性。 - Xeoncross
5
附注:each在PHP 7.2中已被弃用 - Jeff Puckett
4个回答

59

您之前遍历过这个数组吗?each()会记住它在数组中的位置,所以如果您不reset()它,就可能错过一些元素。

reset($array);
while(list($key, $value) = each($array))

值得一提的是,这种遍历数组的方法非常古老,已被更具惯用性的 foreach 取代。除非你明确想要利用它一次只处理一个元素的特性,否则不建议使用。

array each ( array &$array )

从数组中返回当前键值对并将数组光标向前移动。

执行完 each() 后,数组光标将停留在下一个数组元素上,如果到达数组结尾,则停留在最后一个元素的后面。如果要再次使用 each 遍历数组,则需要使用 reset() 重置数组光标。

(来源:PHP 手册)


好的比较。我发现如果需要的话,while each 是从 while 块内部向 $array 添加元素的好方法。 - The Thirsty Ape
非常有趣的比较!每种方法之间的性能差异如何?它们在实际中是否基本相同? - cram2208

14

区别之一是each()只能在数组上使用(而且只有在正确使用时才会生效)。foreach可以在任何实现traversable接口的对象上使用(这当然包括内置的数组类型)。

可能有一个微观优化需要使用 foreach。基本上,foreach等价于以下内容:

$array->rewind();
while ($array->valid()) {
   $key = $array->key();
   $value = $array->current();
   // Do your code here
   $array->next();
}

each 基本上执行以下操作:

$return = $array->valid() ? array($array->key(), $array->current()) : false;
$array->next();
return $return;

对于它们来说,这三行代码是相同的。它们非常相似。也许each不需要考虑traversable接口等微小优化...但这最多只会是一些小的改善。但是与foreach编译的C语言相比,在php代码中执行布尔转换和检查也将被抵消...更不用说在你的while / each代码中,你要调用两个语言结构和一个函数,而foreach只有一个语言结构...

更不用说,在我看来,foreach要容易阅读得多...因此,易于阅读和灵活性更高意味着对我来说foreach是明显的赢家。(这并不是说each没有其用处,但个人从未需要过它)...


这并不完全相同,因为foreach会像在数组的副本中操作一样行事。例如,如果您在迭代过程中更改原始数组的元素,则foreach仍将给您原始数组。只有在开始使用引用(例如foreach($array as &$v){})时,此行为才会更改,因为在这种情况下,您正在表明要更改数组。请参见http://codepad.viper-7.com/bGaIqc - Artefacto
你的回答还有一个问题。数组不实现 Traversable 接口;它们甚至不是对象(试着用具有 Traversable 类型提示的数组构建一个 IteratorIterator,就会发现)。对于 foreach 的目的,它们的行为类似于对象,但在某些情况下,你必须使用 ArrayIterator 将其转换为可遍历的对象。 - Artefacto
内部的Array对象确实实现了Traversable接口。但是你不能将其用作迭代器,因为它没有实现IteratorIteratorAggregate接口... 但在内部,它的功能是相同的... - ircmaxell
很抱歉,你的想法有误。数组不是对象。如果你检查 class_exists("array") 的返回值,你会发现根本不存在 Array 类。 - Artefacto

12

警告! Foreach 会创建 数组的副本,因此您不能在foreach 迭代期间修改它。如果您正在循环遍历其元素和索引并对数组进行实时编辑,则each()仍具有功能,并且非常有用。

// Foreach creates a copy
$array = [
  "foo" => ['bar', 'baz'],
  "bar" => ['foo'],
  "baz" => ['bar'],
  "batz" => ['end']
];

// while(list($i, $value) = each($array)) { // Try this next
foreach($array as $i => $value) {
  print $i . "\n";
  foreach($value as $index) {
    unset($array[$index]);
  }
}

print_r($array); // array('baz' => ['end'])

使用foreachwhile循环都可以完成它们的循环,数组"$array"将会被更改。然而,在foreach循环过程中,即使我们已经删除了元素,它本身不会发生变化。因此,它仍会遍历每个元素。

更新:这个回答没有错误。

我认为这个回答非常直接,但似乎大多数用户无法欣赏我在这里提到的具体细节。

使用libdom (例如删除元素)或其他密集型映射/列表/字典过滤构建应用程序的开发人员可以证明我在这里所说的重要性。

如果您不理解这个答案,它总有一天会让你后悔。


2
可以。但是你可以始终使用 foreach($array as $key => &$value) {},它会给你一个元素的引用。 - Konstantin Bodnia
1
@KonstantinBodnya,你完全没有理解我的回答重点。如果你需要修改数组(而不是元素),你必须使用each() - Xeoncross
将数组元素的引用传递给函数会改变数组。 - Andrew Willis
1
@AndrewWillis,它将改变数组中的元素,但不会改变数组本身。换句话说,您不能执行诸如删除元素之类的操作,从而更改实际的项目数组。 - Xeoncross
1
@A.L,请查看"print $i"的输出以了解区别。这里重要的是要理解对当前正在迭代的实际数组(each())进行修改与对最终输出数组(foreach())进行修改的区别。 - Xeoncross
显示剩余4条评论

1
如果您将一个对象传递给each进行迭代,PHP手册警告可能会产生意外的结果。
那么$array中确切包含什么?

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