PHP Foreach Pass по ссылке: Дублирование последнего элемента? (Ошибка?)
У меня было очень странное поведение с простым php script, который я писал. Я уменьшил его до минимума, необходимого для воссоздания ошибки:
<?php
$arr = array("foo",
"bar",
"baz");
foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);
foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
?>
Выводится:
Array
(
[0] => foo
[1] => bar
[2] => baz
)
Array
(
[0] => foo
[1] => bar
[2] => bar
)
Является ли это ошибкой или каким-то действительно странным поведением, которое должно произойти?
Ответы
Ответ 1
После первого цикла foreach $item
по-прежнему является ссылкой на некоторое значение, которое также используется $arr[2]
. Поэтому каждый вызов foreach во втором цикле, который не вызывает по ссылке, заменяет это значение и, следовательно, $arr[2]
, с новым значением.
Итак, цикл 1, значение и $arr[2]
становятся $arr[0]
, что является "foo".
Петля 2, значение и $arr[2]
становятся $arr[1]
, что является "баром".
Петля 3, значение и $arr[2]
становятся $arr[2]
, что является "баром" (из-за цикла 2).
Значение "baz" фактически теряется при первом вызове второго цикла foreach.
Отладка выходных данных
Для каждой итерации цикла мы будем эхо-значения $item
, а также рекурсивно распечатать массив $arr
.
Когда выполняется первый цикл, мы видим этот вывод:
foo
Array ( [0] => foo [1] => bar [2] => baz )
bar
Array ( [0] => foo [1] => bar [2] => baz )
baz
Array ( [0] => foo [1] => bar [2] => baz )
В конце цикла $item
все еще указывает на то же место, что и $arr[2]
.
Когда выполняется второй цикл, мы видим этот вывод:
foo
Array ( [0] => foo [1] => bar [2] => foo )
bar
Array ( [0] => foo [1] => bar [2] => bar )
bar
Array ( [0] => foo [1] => bar [2] => bar )
Вы заметите, как каждый раз массив помещал новое значение в $item
, он также обновлял $arr[3]
с тем же значением, поскольку оба они все еще указывают на одно и то же местоположение. Когда цикл попадает в третье значение массива, он будет содержать значение bar
, потому что он был просто установлен предыдущей итерацией этого цикла.
Это ошибка?
Нет. Это поведение ссылочного элемента, а не ошибка. Это было бы похоже на запуск чего-то вроде:
for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }
Цикл foreach не является особым в природе, в котором он может игнорировать ссылочные позиции. Он просто устанавливает эту переменную в новое значение каждый раз, как если бы вы не были за пределами цикла.
Ответ 2
$item
является ссылкой на $arr[2]
и перезаписывается вторым циклом foreach, как указывал animuson.
foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);
unset($item); // This will fix the issue.
foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
Ответ 3
Хотя это, возможно, официально не ошибка, по-моему, это так. Я думаю, что проблема здесь в том, что мы ожидаем, что $item
выйдет за пределы области действия, когда цикл будет завершен, как и во многих других языках программирования. Однако, похоже, это не так...
Этот код...
$arr = array('one', 'two', 'three');
foreach($arr as $item){
echo "$item\n";
}
echo $item;
Дает выход...
one
two
three
three
Как уже говорили другие люди, вы переписываете ссылочную переменную в $arr[2]
вторым циклом, но это происходит только потому, что $item
никогда не выходил за рамки. Что вы, ребята, думаете... ошибка?
Ответ 4
Более легкое объяснение, похоже, от Rasmus Lerdorf, оригинального создателя PHP: https://bugs.php.net/bug.php?id=71454
Ответ 5
Правильное поведение PHP может быть ошибкой NOTICE в моем оппионе.
Если ссылочная переменная, созданная в цикле foreach, используется вне цикла, она должна вызывать уведомление.
Очень легко пасть за это поведение, очень сложно определить его, когда это произошло.
И ни один разработчик не собирается читать страницу документации foreach, это не поможет.
Вы должны unset()
ссылку после цикла, чтобы избежать такой проблемы.
unset() в ссылке просто удалит ссылку без ущерба для исходных данных.