PHP Foreach Передача по ссылке: Дублирование последнего элемента? (Ошибка?) - PullRequest
155 голосов
/ 22 ноября 2011

У меня просто было очень странное поведение с простым 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
)

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

Ответы [ 6 ]

168 голосов
/ 22 ноября 2011

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

Таким образом, цикл 1, значение и $arr[2] становятся $arr[0], что является 'foo'.
Цикл 2, значение и $arr[2] становятся $arr[1], что является 'bar'.
Цикл 3, значение и $arr[2] становятся $arr[2], что является'bar' (из-за цикла 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 не является особенным по своей природе, в котором он может игнорировать ссылочные элементы.Это просто установка этой переменной на новое значение каждый раз, как если бы вы были вне цикла.

28 голосов
/ 22 ноября 2011

$item является ссылкой на $arr[2] и перезаписывается вторым циклом foreach, как указал анимусон.

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 голосов
/ 25 ноября 2011

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

Этот код ...

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

Дает вывод ...

one
two
three
three

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

0 голосов
/ 15 апреля 2018

потому что вы используете директиву ref (&).Последнее значение будет заменено вторым циклом, и это повредит ваш массив.самое простое решение - использовать другое имя для второго цикла:

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
0 голосов
/ 28 сентября 2017

Правильное поведение PHP может быть ошибкой УВЕДОМЛЕНИЯ по моему мнению. Если ссылочная переменная, созданная в цикле foreach, используется вне цикла, это должно вызвать уведомление. Очень легко поддаться такому поведению, очень трудно определить, когда это произошло. И ни один разработчик не собирается читать страницу документации по foreach, это не помощь.

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

0 голосов
/ 09 июня 2017

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

...