Неожиданное поведение current () в цикле foreach

Вот простой цикл

$list = array("A", "B", "C","D"); foreach ($list as $var) { print(current($list)); } 

Выход ( демо )

  BBBB // Output for 5.2.4 - 5.5.0alpha4 BCD // Output for 4.4.1 AAAA // Output for 4.3.0 - 4.4.0, 4.4.2 - 5.2.3 

Вопрос:

  • Может кто-нибудь объяснить, что происходит?
  • Почему я не получаю ABCD
  • Даже если копия массива была сделана с помощью foreach я должен получать AAAA но не получать это в текущей стабильной версии PHP

Примечание * Я знаю, что могу просто использовать print $var но из PHP DOC

current – Возвращает текущий элемент в массиве Функция current () просто возвращает значение элемента массива, на который в данный момент указывается внутренний указатель. Он не перемещает указатель. Если внутренний указатель указывает за пределы списка элементов или массив пуст, current () возвращает FALSE.

Обновление 1 – Новое наблюдение

Спасибо Даниэлю Фигероа : просто обернув current в функции, вы получите другой результат

 foreach ( $list as $var ) { print(item($list)); } function item($list) { return current($list); } 

Выход ( демо )

  BCDA // What the hell 

Вопрос:

  • Почему бы не получить «BBBB»?
  • Как ток упаковки в функции влияет на выход foreach ?
  • Откуда появился дополнительный «А»?

Обновление 2

 $list = array("A","B","C","D"); item2($list); function item2($list) { foreach ( $list as $var ) { print(current($list)); } } 

Выход ( см. Демонстрацию )

 AAAA // No longer BBBB when using a function 

Вопрос:

  • Какая разница между запуском цикла в функции и запуском его вне функции, потому что вы получаете AAAA снаружи и BBBB в функции в большинстве версий PHP

Solutions Collecting From Web of "Неожиданное поведение current () в цикле foreach"

Почему это начинается с B?

Поскольку 5.2 foreach (надежно) продвигает указатель массива до начала цикла. См. Также код операции FE_RESET .

 $list = array("A", "B", "C","D"); foreach ($list as $var) { break; } var_dump(current($list)); 

Вывод:

 B 

У этого может быть что-то с тем, как ZEND_OP_DATA псевдо-код ZEND_OP_DATA (который не документирован).

Почему current() продолжает давать одинаковое значение?

Перед запуском цикла foreach создает внутреннюю ссылку на массив, который вы зацикливаете. Как только внутри цикла, всякий раз, когда переменная массива модифицируется или передается по ссылке, внутренняя ссылка диссоциируется из переменной путем создания копии структуры массива (но не элементов). Это скопированное значение сохраняет указатель массива (который ранее был изменен инициализацией цикла).

Это поведение также проявляется при более разрушительной операции unset() :

 $list = array('A', 'B', 'C', 'D'); foreach ($list as $key => $val) { echo $val; unset($list[1], $list[2], $list[3]); } echo "\n", print_r($list, true), "\n"; 

Вывод:

 ABCD Array ( [0] => A ) 

Передача переменной цикла в функцию

Это еще один интересный сценарий:

 $list = array('A', 'B', 'C', 'D'); function itm($arr) { return current($arr); } foreach ($list as $item) { print itm($list); } var_dump(current($list)); 

Вывод:

 BCDA bool(false) 

На этот раз массив передается по значению, и поэтому его структура массива копируется (а не элементы) в параметр $arr . В отличие от предыдущего примера, нет никакой диссоциации между внутренней ссылкой цикла и символом $list поскольку копия имеет место в области функций.

Как насчет последнего "A" ?

Это, безусловно, самое мистифицирующее поведение foreach и может быть засвидетельствовано только в этих обстоятельствах. В последней итерации цикла указатель массива, по- видимому, перематывается на первый элемент; по-видимому, потому что в конце цикла он явно указывает за пределы элементов (как вы можете видеть из последней строки вывода).

Это может иметь какое-то отношение к SWITCH_FREE который выполняется в конце foreach .

Итак, почему размещение foreach в функции делает ее другой?

Соблюдайте следующий код:

 function item2($arr) { foreach ($arr as $var) { print(current($arr)); } var_dump(current($arr)); } $list = array("A","B","C","D"); item2($list); 

Вывод:

 AAAA string(1) "A" 

В этом случае внутренняя ссылка foreach инициализируется копией массива (потому что он имеет номер пересчета> 1) и, таким образом, создает немедленную диссоциацию из символа $arr .

Может ли это ухудшиться?

Конечно! Вы можете получить даже более удачные результаты, когда начнете использовать ссылки или вставьте несколько циклов foreach в одну и ту же переменную.

Итак, как я могу получить согласованные результаты?

Используйте Iterators или не полагайтесь на получение согласованного значения от ссылки на переменную массива во время операции foreach .

ОТ PHP.net

Функция current () просто возвращает значение элемента массива, на который в данный момент указывается внутренний указатель. Он не перемещает указатель каким-либо образом

затем: используйте следующий ()

 $list = array("A", "B", "C","D"); foreach ($list as $var) { print(current($list)); next($list); } 

ПРИМЕЧАНИЕ: первый элемент не будет напечатан, потому что foreach переместил указатель на второй элемент массива 🙂

В этом примере объясняется полное поведение:

 $list = array("A", "B", "C","D"); foreach ($list as $var) { if(!isset($a)) reset($list); $a = 'isset'; print(current($list)); next($list); } 

это ABCD

Также обратите внимание, что:

 As foreach relies on the internal array pointer changing it within the loop may lead to unexpected behavior. 

для каждого


EDIT: Я хочу поделиться и моим новым запутанным находкой !!!

Example1:

 $list = array("A", "B", "C","D"); $list_copy = $list; foreach ($list as $key => $val) { current($list_copy); echo current($list); //next($list); } 

ВЫХОД: AAAA

Example2:

 $list = array("A", "B", "C","D"); $list_copy = $list; foreach ($list as $key => $val) { current($list_copy); echo current($list); next($list); } 

ВЫХОД: ABCD

При вызове функции current () внутри foreach даже для другого массива это повлияет на поведение foreach …

Example3:

 $list = array("A", "B", "C","D"); $refcopy = &$list; foreach ($list as $key => $val) { if(!isset($a)) { $a = 'isset'; reset($list); } echo current($list); next($list); } 

OUTPUT: ACD (WOW! B отсутствует)

Пример: 4

 $list = array("A", "B", "C","D"); $refcopy = &$list; foreach ($list as $key => $val) { echo current($list); next($list); } 

ВЫХОД: BCD

Невозможно точно решить, что произойдет внутри цикла foreach !!!

Ну, я не знаю, почему это так, но я подозреваю, что это может иметь какое-то отношение к тому, как оценивается / обрабатывается присвоение. Для удовольствия я попробовал это, и это привело к еще одному incorrect поведению:

 $arr = array('A', 'B', 'C', 'D'); function itm($val) { return current($val); } foreach ($arr as $item) { print itm($arr); } 

Результат: BCDA

Поэтому я предполагаю, что здесь происходит то, что вызов функции заставляет вычислять токи в правильном порядке. Также причиной для меня получения BCDA вместо ABCD является, вероятно, потому, что внутренний указатель сначала увеличивается (указывая на B), а затем в en возвращается к пункту A.

Возможно, стоит отметить эту строку в документе PHP :

Обратите внимание, что присваивание копирует исходную переменную в новую (присваивание по значению), поэтому изменения в один не влияют на другую. Это может также иметь значение, если вам нужно скопировать что-то вроде большого массива внутри жесткого цикла.

Наверное, это не значит, что это ответ, но мне понравился ваш вопрос и я хотел внести небольшой вклад.

Даже если копия массива была сделана с помощью foreach, я должен получать AAAA, но не получать это в текущей стабильной версии PHP

Поскольку я не нашел ответа на этот вопрос здесь, я попробую объяснить.

Перед первой итерацией $list foreach $list фактически не копируется. Только подсчет ссылок в $list будет увеличен до 2. Итак, на первой итерации: первое значение $list будет скопировано в $var , указатель переместится ко второму элементу $list и будет произведена фактическая копия $list . Поэтому, когда вы вызываете current указательные точки ко второму элементу, но на второй и дальнейших итерациях он никогда не изменяется, потому что фактическая копия $list существует, так что current всегда будет выводить второй элемент.

Редактировать:

Я играл с debug_zval_dump чтобы понять это очень неожиданное поведение:

 <pre> <?php $list = array("A", "B", "C","D"); echo '<h1>Ref count before entering foreach:</h1><br>'; debug_zval_dump($list); echo '<br><br>'; $i = 0; echo '<h1>Ref count in foreach:</h1><br>'; foreach ($list as $var) { $i++; echo '<b>Iteration #'.$i.':</b> '; debug_zval_dump($list); echo '<br>'; } $list = array("A", "B", "C","D"); //re-assign array to avoid confusion echo '<h1>Ref count before entering foreach that calls method "item" and passes array by value:</h1><br>'; debug_zval_dump($list); $i = 0; echo '<h1>Ref count in foreach that calls method "item" and passes array by value:</h1><br>'; foreach ( $list as $var ) { $i++; item($list, $i); } function item($list, $i) { echo '<b>Iteration #'.$i.':</b> '; debug_zval_dump($list); } $list = array("A", "B", "C","D"); //re-assign array to avoid confusion echo '<h1>Ref count before entering foreach that calls method "item" and passes array by reference:</h1><br>'; debug_zval_dump($list); $i = 0; echo '<h1>Ref count in foreach that calls method "item" and passes array by reference:</h1><br>'; foreach ( $list as $var ) { $i++; itemWithRef($list, $i); } function itemWithRef(&$list, $i) { echo '<b>Iteration #'.$i.':</b> '; debug_zval_dump($list); } 

И получил следующий результат:

  Счет перед вводом foreach: 
array (4) refcount (2) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) }

Количество ссылок в foreach:
Итерация # 1: массив (4) refcount (3) { [0] => string (1) «A» refcount (2) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) }
Итерация # 2: массив (4) refcount (3) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (2) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) }
Итерация № 3: массив (4) refcount (3) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (2) [3] => строка (1) «D» refcount (1) }
Итерация # 4: массив (4) refcount (3) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (2) }
Ref count перед вводом foreach, который вызывает метод «item» и передает массив по значению:
array (4) refcount (2) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) } Счетчик ссылок в foreach, который вызывает метод «item» и передает массив по значению:
Итерация № 1: массив (4) refcount (5) { [0] => string (1) «A» refcount (2) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) } Итерация # 2: массив (4) refcount (5) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (2) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) } Итерация № 3: массив (4) refcount (5) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (2) [3] => строка (1) «D» refcount (1) } Итерация # 4: массив (4) refcount (5) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (2) } Счетчик ссылок перед вводом foreach, который вызывает метод «item» и передает массив по ссылке:
array (4) refcount (2) { [0] => string (1) «A» refcount (1) [1] => строка (1) «B» refcount (1) [2] => строка (1) «C» refcount (1) [3] => строка (1) «D» refcount (1) } Счетчик ссылок в foreach, который вызывает метод «item» и передает массив по ссылке:
Итерация # 1: массив (4) refcount (1) { [0] => string (1) «A» refcount (4) [1] => строка (1) «B» refcount (3) [2] => строка (1) «C» refcount (3) [3] => строка (1) «D» refcount (3) } Итерация # 2: массив (4) refcount (1) { [0] => string (1) «A» refcount (3) [1] => строка (1) «B» refcount (4) [2] => строка (1) «C» refcount (3) [3] => строка (1) «D» refcount (3) } Итерация № 3: массив (4) refcount (1) { [0] => string (1) «A» refcount (3) [1] => строка (1) «B» refcount (3) [2] => строка (1) «C» refcount (4) [3] => строка (1) «D» refcount (3) } Итерация № 4: массив (4) refcount (1) { [0] => string (1) «A» refcount (3) [1] => строка (1) «B» refcount (3) [2] => строка (1) «C» refcount (3) [3] => строка (1) «D» refcount (4) }

Результат немного запутан.

В первом примере foreach создал внутреннюю копию $list поэтому счетчик ссылок был 2 (4 в результате, потому что debug_zval_dump добавляет один refCount ). Во втором примере (pass by value) refCount увеличилось до 3, поскольку $list был скопирован для функции. В третьем примере count хранится в 1, потому что $list был передан по значению. Мне нужно время, чтобы понять, почему. Если вы получите точку из этой итоговой доли.

Все, что я могу сказать, это то, что когда мы передавали массив по значению foreach проходил массив, который выполнял итерацию, но когда он передавался по ссылке, он принимал исходный $list . Вопрос в том, почему foreach передавал этот массив?

Код, который вы используете, если ложь. Даже в буквальном смысле это может выглядеть как один и тот же код, однако переменные не являются ( http://3v4l.org/jainJ ).

Чтобы ответить на ваш реальный вопрос, для согласованных результатов используйте правильные инструменты.

Если вам нужна переменная со значением массива, назначьте ее:

 $list = array(....); 

Если вам нужно получить текущее значение этого массива, используйте его перед foreach :

 $current = current($list); 

Поскольку внутри foreach это может быть одно и то же имя переменной, но значение будет другим (представьте, вы повторяете!).

Если вам нужно текущее значение на каждую итерацию, используйте его:

 foreach ($list as $current) { ... } 

См. $current ?

О, черт возьми, да, это было так просто. Подождите, у меня уже есть последовательные результаты. О, и было так легко не обмануть себя. Ура! 😉

Для журнала: Передача переменной в качестве параметра функции делает ее новой переменной. Даже когда ссылка (что объясняется).

Если вы сомневаетесь, не используйте ссылки PHP. Или даже переменные: http://3v4l.org/6p5nZ

Замечательно. Но, похоже, проблема с памятью связана с другой версией php. Также ток дает только текущую позицию, которую вы не увеличиваете (перемещаетесь) в любом месте, поэтому не получаете надлежащего вывода. Поскольку другая версия php интерпретирует следующую и начальную точку массива по-разному, решение этого может быть сбросом внутри цикла с некоторым условием. (кстати, цикл, а затем с использованием текущего, следующий prev – не лучший способ, поскольку уже есть объект в var :), что бы вы ни выбрали) Это один из способов заставить его работать:

 <?php $list = array("A", "B", "C","D"); $flag =0; foreach ($list as $var) { if($flag==0) { reset($list); $flag=1; } print(current($list)); next($list); } 

Выход – ABCD. См. http://3v4l.org/5Hm5Y

 $list = array("A", "B", "C","D"); foreach ($list as $var) { echo $var; } 

Должен это делать.

Использование Это вы уже знаете, что происходит!

 $list = array('A', 'B', 'C','D'); foreach ($list as $var) { var_dump(current($list)); } 

может быть, это поможет вам!