我一直在想这个问题。我通常会根据自己的直觉,在遍历数组键的同时更改数组:

可以在foreach调用中通过引用传递数组项:

foreach (array_keys($array) as $key) {
  $array[$key] = perform_changes_on($array[$key]);
}


最后,可以在循环期间直接修改数组:
foreach ($array as &$item) {
  $item = perform_changes_on($item);  
}


每种方法对性能和安全性有何影响?有没有推荐的方法?

更新:我实际上担心的是,在循环时,最后两种方法会修改数组。

评论

stackoverflow.com/questions/10057671/how-foreach-actually-works很有帮助。

OP提供了实际的代码,但是由于代码不是“具体实现”的一部分而被关闭了吗?老实说,什么?

#1 楼

如果您担心在循环时修改数组,请不要这样做,因为foreach会复制数组。尝试

$array = array('foo');
foreach ($array as $k => &$item) {
  $array[] = 'bar';
}
var_dump($array);


,看看它终止得很好。 foreach ($array as $k => &$v)foreach (array_keys($array) as $k) $v = &$array[$k]的简写,因此虽然仍然有数组的副本(这就是为什么我在示例中使用&$item的原因,所以您可以看到,如果您修改了数组,那么它将在引用中被修改!

$array = array('foo', 'bar');
foreach ($array as $k => $item) {
  echo "$item\n";
  if (!$k) {
    $array[1] = 'baz';
  }
}
$array = array('foo', 'bar');
foreach ($array as $k => &$item) {
  echo "$item\n";
  if (!$k) {
    $array[1] = 'baz';
  }
}


第一个转储foo和bar,第二个foo和baz。

评论


\ $ \ begingroup \ $
“ foreach($ array as $ k => $ v)是foreach(array_keys($ array)as $ k)$ v =&$ array [$ k]的简写。”与调用函数和语言构造的区别不大一样(尽管出于实际原因,我认为这只是简写)。同样,它是副本的简写,而不是参考。 (此外,PHP是写时复制的,因此您通常可以忽略多余的副本。)
\ $ \ endgroup \ $
– Corbin
2012年6月20日在1:46



\ $ \ begingroup \ $
显然,这是要对foreach($ array如$ k =>&$ v)进行编辑和修复的。
\ $ \ endgroup \ $
–chx
2012年6月20日下午5:46

\ $ \ begingroup \ $
要指出,if(!$ k)对于0为TRUE,否则该语句始终为false。因此,实质上,您在看到第二个数组元素之前就将其覆盖。
\ $ \ endgroup \ $
– mseancole
2012年6月20日下午13:31

\ $ \ begingroup \ $
我做到了,并且这样做是为了证明第一个foreach在副本中有效,而第二个则不能。
\ $ \ endgroup \ $
–chx
2012年6月20日在16:39

\ $ \ begingroup \ $
@chx然后您可能要运行第一个代码段。 $ item是$ array中值的副本。 $ array永远不会被复制(尽管如果foreach循环完成,将复制$ array的所有元素)。第一个片段将像第二个一样转储foo和baz。
\ $ \ endgroup \ $
– Corbin
2012年6月22日,下午3:33

#2 楼

这听起来像是放置array_map()的地方。

$array = array_map('perform_changes_on', $array);


评论


\ $ \ begingroup \ $
很好,但是有时候我不会更改数组中的每个项目。我只是想使示例更简单...
\ $ \ endgroup \ $
– Capi Etheriel
2012年6月18日17:55

\ $ \ begingroup \ $
perfom_changes_on不必更改每个元素。请注意,还有其他数组函数,其中一些函数比其他函数更强大。例如,array_reduce可以有很大帮助。
\ $ \ endgroup \ $
– Quentin Pradet
2012年6月19日在11:46

\ $ \ begingroup \ $
这不涉及对每个元素的函数调用吗?可能不是最好的性能明智的选择。
\ $ \ endgroup \ $
–汤姆
2014年12月8日在18:31

\ $ \ begingroup \ $
@Tom:我不会说函数调用的开销很明显。可维护性比非常小的性能提升更重要(我什至不确定它们是否确实存在)。但是,如果您认为编写不带函数的100,000行程序是最好的编程方法,则一定要这样做。当没有人想要维护代码时,不要感到惊讶,而且一天后您将不明白自己写的内容。
\ $ \ endgroup \ $
– Konrad Borowski
2014-12-8 20:34



#3 楼

使用foreach($array as &$item)时,切勿忘记在foreach之后使用unset($item);,否则以后尝试使用$item时会遇到严重麻烦。

通常应该避免foreach ...&并执行array_walk($array, function (&$item) {...,以便将引用严格限制在闭包内。

评论


\ $ \ begingroup \ $
感谢您的回答,我正在使用array_keys来避免陷阱,但是我需要使用它吗?我的意思是,foreach(将$ array作为$ key => $ item)应该让我更改$ array [$ key]的值,但是谁知道php在内部如何工作……实际上就是问题所在。
\ $ \ endgroup \ $
– Capi Etheriel
2012年6月19日在17:02

#4 楼

对于性能方面,我真的不能帮您,只能告诉您将它们包装在microtime()标签中,然后看看哪个对您来说效果更好。系统略有不同。我可以给您的一个建议是从您的代码中删除array_keys()。为您的答案。我正在寻找,foreach感到困惑。 For和while循环确实会在每次迭代中调用作为参数传入的任何函数,而foreach则不会。这就是为什么最好在for或while循环之外调用strlen()count()之类的函数来给出几个示例的原因。我们遇到的开销不是来自foreach,而是来自array_keys()。调用array_keys()时,它必须生成一个新的数组,这就是为什么它几乎慢一倍的原因。因此,最好将所有array_keys()函数全部删除,然后仅遍历数组并检索键值对。很抱歉造成任何混乱。

资料来源: .com / questions / 3430194 / performance-of-for-vs-foreach-in-php
https://stackoverflow.com/questions/1740575/php5-does-the-copy-of-an-array- for-for-each-incur-overhead

!!更新结束!

据我所知,所有这些实现都没有安全风险。您正在迭代一个已经存在的构造。在此之前,任何安全问题都会发生。当然,如果您使用的是用户提供的数据,例如GET和POST。您应该在使用之前清理并验证这些内容,这可以通过其中的每个foreach循环来完成。或者您也可以查看filter_input_array()及其表亲。

我知道由于缺乏可读性,我个人不会使用第二种实现。至少在第一个和第三个实现中,您可以直观地看到一个值正在更改。第二个不是很明显。但是,很有可能效率更高。我自己使用了第一个和第三个,但更经常使用第三个。认为这与我的心情有关。希望这会有所帮助,我很想听听其他人可能要说的话:)

评论


\ $ \ begingroup \ $
“给foreach的参数是在每次迭代中声明的,因此从技术上讲,每次foreach循环时,您都在调用array_keys()。”不,在这种情况下,它将仅调用一次array_keys。 (也许在PHP5之前它就具有这种行为,但我不这样认为,因为这很容易以无限循环结束。)
\ $ \ endgroup \ $
– Corbin
2012年6月18日17:11

\ $ \ begingroup \ $
@Corbin:不,仍然是一个问题。不相信我,对其进行一些测试,您会发现自己的。 PHP每次循环时都会为第一个参数创建一个迭代器对象,以便它可以在每次迭代时执行each()。它被重新创建,但指针保持不变。这就是为什么如果您通过引用更改了数组的值,那么它将在下一次迭代时立即可用:)
\ $ \ endgroup \ $
– mseancole
2012年6月18日在18:09

\ $ \ begingroup \ $
您能举个例子吗?只是试图让它两次调用array_keys(好吧,f),却永远无法实现。
\ $ \ endgroup \ $
– Corbin
2012年6月18日在18:20

\ $ \ begingroup \ $
潜在相关:stackoverflow.com/questions/4043569/…
\ $ \ endgroup \ $
– Corbin
2012年6月18日18:35

\ $ \ begingroup \ $
@Corbin:老实说,我不确定。我见过有人声称完全相反,而我的速度测试似乎证明了这一点。具有“重新创建的”数组的foreach似乎总是需要更长的时间。进行了超过一百次迭代的测试,所有结果似乎都证明了在后台执行了额外的操作。我仍在寻找该职位,希望可以找到它。我需要开始保留这些帖子的存储库...
\ $ \ endgroup \ $
– mseancole
2012年6月18日19:17



#5 楼

我会采用第二种方法:

foreach ($array as &$item) {
  $item = perform_changes_on($item);  
}


甚至更好:

function perform_changes_on(&$item) {
// ...
}

foreach ($array as &$item) {
  perform_changes_on($item);  
}
// ...


item(按引用)似乎是最安全的一项,因为您不访问数组的结构,而是访问其单个元素(我想这就是您想要的)。