让我通过说我知道foreach
是什么,做什么以及如何使用它来为此加上前缀。这个问题涉及它如何在引擎盖下工作,我不想要任何答案,“这是你用foreach
循环数组的方式。”
很长一段时间我都认为foreach
使用了数组本身。然后我发现许多引用它与数组的副本一起工作的事实,我已经假设这是故事的结尾。但是我最近讨论了这个问题,经过一些实验后发现事实并非如此。
让我说明我的意思。对于以下测试用例,我们将使用以下数组:
$array = array(1, 2, 3, 4, 5);
foreach ($array as $item) {
echo "$item\n";
$array[] = $item;
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 2 3 4 5 1 2 3 4 5 */
这清楚地表明我们没有直接使用源数组 - 否则循环将永远持续,因为我们在循环期间不断将项目推送到数组。但只是为了确保这种情况:
foreach ($array as $key => $item) {
$array[$key + 1] = $item + 2;
echo "$item\n";
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 3 4 5 6 7 */
这支持了我们的初步结论,我们在循环期间使用源数组的副本,否则我们将在循环期间看到修改后的值。 但是...... 的
如果我们查看manual,我们会发现此声明:
当foreach首次开始执行时,内部数组指针会自动重置为数组的第一个元素。
正确......这似乎表明foreach
依赖于源数组的数组指针。但我们刚刚证明我们没有使用源数组,对吧?嗯,不完全是。
// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));
foreach ($array as $item) {
echo "$item\n";
}
var_dump(each($array));
/* Output
array(4) {
[1]=>
int(1)
["value"]=>
int(1)
[0]=>
int(0)
["key"]=>
int(0)
}
1
2
3
4
5
bool(false)
*/
因此,尽管我们没有直接使用源数组,但我们直接使用源数组指针 - 指针位于循环结尾处数组末尾的事实显示了这一点。除非这不是真的 - 如果是,那么test case 1将永远循环。
PHP手册还指出:
由于foreach依赖于内部数组指针,因此在循环内更改它可能会导致意外行为。
好吧,让我们找出那种“意外行为”是什么(从技术上讲,任何行为都是出乎意料的,因为我不再知道会发生什么)。
foreach ($array as $key => $item) {
echo "$item\n";
each($array);
}
/* Output: 1 2 3 4 5 */
foreach ($array as $key => $item) {
echo "$item\n";
reset($array);
}
/* Output: 1 2 3 4 5 */
......没有那么出乎意料的事实,它实际上似乎支持“源头复制”理论。
问题
这里发生了什么?我的C-fu不足以让我能够通过查看PHP源代码来提取正确的结论,如果有人能为我翻译成英文,我将不胜感激。
在我看来,foreach
与数组的副本一起工作,但在循环之后将源数组的数组指针设置为数组的末尾。
each()
期间使用调整数组指针(reset()
,foreach
等)的函数是否会影响循环的结果?答案 0 :(得分:107)
在示例3中,您不能修改数组。在所有其他示例中,您可以修改内容或内部数组指针。由于赋值运算符的语义,这对于PHP数组很重要。
PHP中数组的赋值运算符更像是一个惰性克隆。与大多数语言不同,将一个变量分配给包含数组的另一个变量将克隆该数组。但是,除非需要,否则不会进行实际克隆。这意味着只有在修改了任何一个变量(写时复制)时才会发生克隆。
以下是一个例子:
$a = array(1,2,3);
$b = $a; // This is lazy cloning of $a. For the time
// being $a and $b point to the same internal
// data structure.
$a[] = 3; // Here $a changes, which triggers the actual
// cloning. From now on, $a and $b are two
// different data structures. The same would
// happen if there were a change in $b.
回到您的测试用例,您可以轻松地想象foreach
创建某种迭代器并引用该数组。此引用与我的示例中的变量$b
完全相同。但是,迭代器和引用只在循环期间存在,然后它们都被丢弃。现在你可以看到,在所有情况下,除了3之外,数组在循环期间被修改,而这个额外的引用是活的。这会触发一个克隆,并解释这里发生了什么!
这是一篇关于这种写时复制行为的另一个副作用的优秀文章:The PHP Ternary Operator: Fast or not?
答案 1 :(得分:43)
使用foreach()
时需要注意的几点:
a)foreach
适用于原始数组的预期副本。
这意味着foreach()
将存储SHARED数据,直到prospected copy
为止
没有创建foreach Notes/User comments。
b)触发预期副本的原因是什么?
根据{{1}}的策略创建预期副本,即,何时
传递给copy-on-write
的数组发生了变化,创建了原始数组的克隆。
c)原始数组和foreach()
迭代器将具有foreach()
,即一个用于原始数组,另一个用于DISTINCT SENTINEL VARIABLES
;请参阅下面的测试代码。 SPL,Iterators和Array Iterator。
Stack Overflow问题 How to make sure the value is reset in a 'foreach' loop in PHP? 解决了您问题的案例(3,4,5)。
以下示例显示each()和reset()不会影响foreach
个变量
SENTINEL
迭代器的(for example, the current index variable)
。
foreach()
<强>输出:强>
$array = array(1, 2, 3, 4, 5);
list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";
foreach($array as $key => $val){
echo "foreach: $key => $val<br/>";
list($key2,$val2) = each($array);
echo "each() Original(inside): $key2 => $val2<br/>";
echo "--------Iteration--------<br/>";
if ($key == 3){
echo "Resetting original array pointer<br/>";
reset($array);
}
}
list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";
答案 2 :(得分:29)
PHP 7的注释
要更新这个答案,因为它已经获得了一些流行:这个答案不再适用于PHP 7.如“Backward incompatible changes”中所述,在PHP 7中,foreach可以处理数组的副本,所以任何数组本身的更改不会反映在foreach循环上。链接的更多细节。
解释(引自php.net):
第一个表单循环遍历array_expression给出的数组。在每一个上 迭代时,当前元素的值被赋值给$ value和 内部数组指针前进一个(所以下一个 迭代,你将看到下一个元素。)
所以,在你的第一个例子中,你只有一个元素在数组中,并且当指针被移动时,下一个元素不存在,所以在你添加新元素foreach之后,因为它已经“决定”它作为最后一个元素。
在你的第二个例子中,你从两个元素开始,并且foreach循环不在最后一个元素,因此它在下一次迭代时评估数组,从而意识到数组中有新元素。
我认为这是文档中解释的每次迭代部分的结果,这可能意味着foreach
在调用{{1}中的代码之前完成所有逻辑}}
测试用例
如果你运行:
{}
您将获得此输出:
<?
$array = Array(
'foo' => 1,
'bar' => 2
);
foreach($array as $k=>&$v) {
$array['baz']=3;
echo $v." ";
}
print_r($array);
?>
这意味着它接受了修改并经历了它,因为它是“及时”修改的。但如果你这样做:
1 2 3 Array
(
[foo] => 1
[bar] => 2
[baz] => 3
)
你会得到:
<?
$array = Array(
'foo' => 1,
'bar' => 2
);
foreach($array as $k=>&$v) {
if ($k=='bar') {
$array['baz']=3;
}
echo $v." ";
}
print_r($array);
?>
这意味着数组被修改了,但是因为我们在1 2 Array
(
[foo] => 1
[bar] => 2
[baz] => 3
)
已经在数组的最后一个元素时修改它,所以它“决定”不再循环,即使我们添加了新元素,我们加上它“为时已晚”而且没有通过。
详细解释可以在How does PHP 'foreach' actually work?阅读,它解释了这种行为背后的内部原因。
答案 3 :(得分:14)
根据PHP手册提供的文档。
在每次迭代中,当前元素的值分配给$ v和内部
数组指针先进一步(所以在下一次迭代中,你将看到下一个元素)。
按照你的第一个例子:
$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
$array['bar']=2;
echo($v);
}
$array
只有单个元素,因此根据foreach执行,1分配给$v
并且它没有任何其他元素来移动指针
但是在你的第二个例子中:
$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
$array['baz']=3;
echo($v);
}
$array
有两个元素,所以现在$ array计算零索引并将指针移动一个。对于循环的第一次迭代,将$array['baz']=3;
添加为引用传递。
答案 4 :(得分:11)
很好的问题,因为许多开发人员,甚至是经验丰富的开发人员都对PHP在foreach循环中处理数组的方式感到困惑。在标准的foreach循环中,PHP生成循环中使用的数组的副本。循环结束后立即丢弃副本。这在简单的foreach循环的操作中是透明的。 例如:
$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
echo "{$item}\n";
}
输出:
apple
banana
coconut
因此创建副本但开发人员没有注意到,因为原始数组未在循环内引用或在循环结束后引用。但是,当您尝试修改循环中的项目时,您会发现它们在完成时未经修改:
$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
$item = strrev ($item);
}
print_r($set);
输出:
Array
(
[0] => apple
[1] => banana
[2] => coconut
)
原始版本的任何更改都不能是通知,实际上原始版本没有任何更改,即使您明确为$ item指定了值。这是因为您正在操作$ item,因为它出现在正在处理的$ set副本中。您可以通过引用抓取$ item来覆盖它,如下所示:
$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
$item = strrev($item);
}
print_r($set);
输出:
Array
(
[0] => elppa
[1] => ananab
[2] => tunococ
)
因此很明显且可观察到,当$ item按引用操作时,对$ item的更改将发送给原始$ set的成员。通过引用使用$ item也会阻止PHP创建数组副本。为了测试这一点,首先我们将展示一个演示副本的快速脚本:
$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
$set[] = ucfirst($item);
}
print_r($set);
输出:
Array
(
[0] => apple
[1] => banana
[2] => coconut
[3] => Apple
[4] => Banana
[5] => Coconut
)
正如示例中所示,PHP复制了$ set并使用它进行循环,但是当在循环内部使用$ set时,PHP将变量添加到原始数组,而不是复制的数组。基本上,PHP只使用复制的数组来执行循环和$ item的赋值。因此,上面的循环只执行3次,每次它将另一个值附加到原始$ set的末尾,原始的$ set保留6个元素,但从不进入无限循环。
但是,如前所述,如果我们按引用使用$ item会怎么样?在上述测试中添加了一个字符:
$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
$set[] = ucfirst($item);
}
print_r($set);
导致无限循环。请注意,这实际上是一个无限循环,你必须自己杀死脚本或等待你的操作系统耗尽内存。我在我的脚本中添加了以下行,因此PHP会很快耗尽内存,如果您要运行这些无限循环测试,我建议您也这样做:
ini_set("memory_limit","1M");
所以在前面这个带有无限循环的例子中,我们看到为什么编写PHP来创建要循环的数组副本的原因。当副本被创建并仅由循环结构本身的结构使用时,该数组在循环执行期间保持静态,因此您永远不会遇到问题。
答案 5 :(得分:7)
PHP foreach循环可以与Indexed arrays
,Associative arrays
和Object public variables
一起使用。
在foreach循环中,php所做的第一件事就是它创建了一个要迭代的数组副本。 PHP然后迭代数组的新copy
而不是原始数组。这在以下示例中进行了演示:
<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
$numbers[$index] = $number + 1; # this is making changes to the origial array
echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).
除此之外,php也允许使用iterated values as a reference to the original array value
。这在下面说明:
<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
++$number; # we are incrementing the original value
echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value
注意:不允许将original array indexes
用作references
。
来源:http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples