PHP Foreach Pass по ссылке: Дублирование последнего элемента? (Ошибка?)

У меня просто было очень странное поведение с простым скриптом php, который я писал. Я уменьшил его до минимума, необходимого для воссоздания ошибки:

<?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 ) 

Это ошибка или какое-то действительно странное поведение, которое должно произойти?

После первого цикла foreach $item все еще является ссылкой на некоторое значение, которое также используется $arr[2] . Поэтому каждый вызов foreach во втором цикле, который не вызывает по ссылке, заменяет это значение и, таким образом, $arr[2] , с новым значением.

Итак, цикл 1, значение и $arr[2] становятся $arr[0] , что является «foo».
Loop 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 не является особым по своему характеру, в котором он может игнорировать ссылочные позиции. Это просто устанавливает эту переменную в новое значение каждый раз, как если бы вы были вне цикла.

$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? с 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? 

Хотя это может быть официально не ошибкой, на мой взгляд, это так. Я думаю, что проблема здесь в том, что мы ожидаем, что $item выйдет из области действия, когда цикл будет завершен, как и во многих других языках программирования. Однако, похоже, это не так …

Этот код …

 $arr = array('one', 'two', 'three'); foreach($arr as $item){ echo "$item\n"; } echo $item; 

Дает результат …

 one two three three 

Как уже говорили другие люди, вы переписываете ссылочную переменную в $arr[2] вторым циклом, но это происходит только потому, что $item никогда не выходил за рамки. Что вы, ребята, думаете … ошибка?

Более легкое объяснение, похоже, от Rasmus Lerdorf, оригинального создателя PHP: https://bugs.php.net/bug.php?id=71454

Правильное поведение PHP может быть ошибкой NOTICE в моем оппионе. Если ссылочная переменная, созданная в цикле foreach, используется вне цикла, она должна вызывать уведомление. Очень легко пасть за это поведение, очень сложно определить его, когда это произошло. И ни один разработчик не собирается читать страницу документации foreach, это не поможет.

Вы должны unset() ссылку после цикла, чтобы избежать такой проблемы. unset () по ссылке просто удалит ссылку без ущерба для исходных данных.