it-swarm.com.ru

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

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

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

154
regality

После первого цикла 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 не является особенным по своей природе, в котором он может игнорировать ссылочные элементы. Это просто установка этой переменной на новое значение каждый раз, как если бы вы были вне цикла.

166
animuson

$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?
28
Michael Leaney

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

Этот код ...

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

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

one
two
three
three

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

3
jocull

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

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

foreach ($arr as $anotherItem) { ... }
0
Amir Surnay

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

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

0
John

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

0
qdinar