Подтвердить что ты не робот

Неожиданное поведение 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 - Новое наблюдение

Благодаря Daniel Figueroa: просто обернув 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
4b9b3361

Ответ 1

Почему это начинается с 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 (который действительно не документирован).

Почему 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 инициализируется копией массива (потому что он имеет refcount > 1) и, таким образом, создает немедленную диссоциацию из символа $arr.

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

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

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

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

Ответ 2

FROM PHP.net

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

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

$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);
}

out put - ABCD

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

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

foreach


РЕДАКТИРОВАТЬ: Я хочу поделиться своим новым запутанным нахождением!!!

Пример1:

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

ВЫХОД: AAAA

Пример2:

$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...

Пример3:

$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);
}

OUTPUT: BCD

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

Ответ 3

Ну, у меня нет реального понятия, почему это так, но я подозреваю, что это может иметь какое-то отношение к тому, как оценивается/обрабатывается присвоение. Для удовольствия я попробовал это, и это привело к другому поведению 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 это reset назад, чтобы указать на A.

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

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

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

Ответ 4

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

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

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

Edit:

Я играл с 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);
}

И получил следующий вывод:

Ref count before entering foreach:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }

Ref count in foreach:
Iteration #1: array(4) refcount(3){ [0]=> string(1) "A" refcount(2) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }
Iteration #2: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(2) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }
Iteration #3: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(2) [3]=> string(1) "D" refcount(1) }
Iteration #4: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(2) }
Ref count before entering foreach that calls method "item" and passes array by value:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Ref count in foreach that calls method "item" and passes array by value:
Iteration #1: array(4) refcount(5){ [0]=> string(1) "A" refcount(2) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Iteration #2: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(2) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Iteration #3: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(2) [3]=> string(1) "D" refcount(1) } Iteration #4: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(2) } Ref count before entering foreach that calls method "item" and passes array by reference:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Ref count in foreach that calls method "item" and passes array by reference:
Iteration #1: array(4) refcount(1){ [0]=> string(1) "A" refcount(4) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(3) } Iteration #2: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(4) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(3) } Iteration #3: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(4) [3]=> string(1) "D" refcount(3) } Iteration #4: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(4) }

Выход немного запутан.

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

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

Ответ 5

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

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

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

$list = array(....);

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

$current = current($list);

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

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

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

См. $current?

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

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

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

Ответ 6

Отличная точка. Но, похоже, проблема с памятью связана с другой версией php. Также текущий дает только текущую позицию, которую вы не увеличиваете (перемещаетесь) в любом месте, поэтому не получаете надлежащего вывода. Поскольку другая версия php интерпретирует следующую и начальную точку массива по-разному, решение этого может быть reset внутри цикла с некоторым условием. (кстати, цикл, а затем с использованием текущего, следующий 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

Ответ 7

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

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

Ответ 8

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

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

может помочь вам!