【问题标题】:Unexpected behaviour of current() in a foreach loop [duplicate]foreach 循环中 current() 的意外行为[重复]
【发布时间】:2013-01-28 17:24:09
【问题描述】:

这是一个简单的循环

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

输出 (demo)

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

输出(Demo

 BCDA   // What the hell 

问题:

  • 为什么没有得到“BBBB”?
  • 在函数中封装电流如何影响foreach 输出?
  • 多余的“A”从何而来?

更新 2

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

输出(See Demo

AAAA // No longer BBBB when using a function

问题:

  • 在函数中运行循环和在函数外运行它有什么不同,因为在大多数 PHP 版本中,你在外部得到 AAAA 而在函数中得到 BBBB

【问题讨论】:

标签: php arrays loops foreach


【解决方案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 最神秘的行为,只能在这种情况下目睹。在最后一次循环迭代中,数组指针看似倒回到了第一项;似乎是因为在循环结束时它显然指向元素末尾之外(从输出的最后一行可以看到)。

这可能与在foreach 末尾执行的SWITCH_FREE 操作码有关。

那么为什么将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 操作期间通过引用数组变量获得一致的值。

【讨论】:

  • +很好,但它如何解释stackoverflow.com/a/14849560/1226894
  • 我不明白为什么rewind 来自?这是否意味着函数倒带数组???或 return 语句对数组有影响
  • 你知道在函数中运行循环会给出 3 个不同的结果 -3v4l.org/1aUpd 但迭代器给出相同的结果 - 3v4l.org/ViCNn
  • @Baba 是的,我也注意到了;迭代器更加稳定:)
  • 但它们不能与引用一起使用... :(
【解决方案2】:

来自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);
}

输出是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

在 foreach 中调用 current() 函数时,即使是另一个数组也会影响 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);
}

输出:ACD(哇!B 不见了)

示例:4

$list = array("A", "B", "C","D");

$refcopy = &$list;

foreach ($list as $key => $val) {
  echo current($list);
  next($list);
}

输出: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 中它被重置回指向 A。

    PHP doc 中的这一行可能值得注意:

    请注意,赋值会将原始变量复制到新变量(按值赋值),因此对一个变量的更改不会影响另一个。如果您需要在紧密循环中复制大型数组之类的内容,这也可能具有相关性。

    我想这并不能真正算作一个答案,但我喜欢你的问题并想贡献一点。

    【讨论】:

    • 很好,这变得复杂了....很好的观察
    • 确实,因为我认为软拷贝参数现在有点无效。
    • 会将您的观察添加到问题中......这是关键......观察
    • @DanielFigueroa 我也这么认为。我刚刚尝试通过引用传递$arr,它输出BBBBcodepad.viper-7.com/6MxdFr
    【解决方案4】:

    即使数组的副本是由 foreach 制作的,我也应该得到 AAAA,但在当前 PHP 稳定版本中没有得到它

    由于我在这里没有找到这个问题的答案,我将(尝试)解释一下。

    foreach 的第一次迭代之前$list 实际上并没有被复制。只有$listreference counting 会增加到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]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) }

    foreach 中的引用计数:
    迭代 #1: array(4) refcount(3){ [0]=> 字符串(1)“A”引用计数(2) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) }
    迭代 #2: array(4) refcount(3){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(2) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) }
    迭代 #3: array(4) refcount(3){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(2) [3]=> 字符串(1)“D”引用计数(1) }
    迭代 #4: array(4) refcount(3){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(2) }
    进入调用方法“item”并按值传递数组的foreach之前的引用计数:
    array(4) refcount(2){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) } 调用方法“item”并按值传递数组的 foreach 中的引用计数:
    迭代 #1: array(4) refcount(5){ [0]=> 字符串(1)“A”引用计数(2) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) } 迭代 #2: array(4) refcount(5){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(2) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) } 迭代 #3: array(4) refcount(5){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(2) [3]=> 字符串(1)“D”引用计数(1) } 迭代 #4: array(4) refcount(5){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(2) } 进入调用方法“item”并通过引用传递数组的foreach之前的引用计数:
    array(4) refcount(2){ [0]=> 字符串(1)“A”引用计数(1) [1]=> 字符串(1)“B”引用计数(1) [2]=> 字符串(1)“C”引用计数(1) [3]=> 字符串(1)“D”引用计数(1) } 调用方法“item”并通过引用传递数组的 foreach 中的引用计数:
    迭代 #1: array(4) refcount(1){ [0]=> 字符串(1)“A”引用计数(4) [1]=> 字符串(1)“B”引用计数(3) [2]=> 字符串(1)“C”引用计数(3) [3]=> 字符串(1)“D”引用计数(3) } 迭代 #2: array(4) refcount(1){ [0]=> 字符串(1)“A”引用计数(3) [1]=> 字符串(1)“B”引用计数(4) [2]=> 字符串(1)“C”引用计数(3) [3]=> 字符串(1)“D”引用计数(3) } 迭代 #3: array(4) refcount(1){ [0]=> 字符串(1)“A”引用计数(3) [1]=> 字符串(1)“B”引用计数(3) [2]=> 字符串(1)“C”引用计数(4) [3]=> 字符串(1)“D”引用计数(3) } 迭代 #4: array(4) refcount(1){ [0]=> 字符串(1)“A”引用计数(3) [1]=> 字符串(1)“B”引用计数(3) [2]=> 字符串(1)“C”引用计数(3) [3]=> 字符串(1)“D”引用计数(4) }

    输出有点混乱。

    在第一个示例中,foreach 创建了 $list 的内部副本,因此引用计数为 2(结果中为 4,因为 debug_zval_dump 添加了一个 refCount)。在第二个示例中(按值传递)refCount 增加到 3,因为 $list 被复制用于函数。在第三个示例中,计数保持为 1,因为 $list 是按值传递的。我需要一些时间来了解原因。如果你从这个结果分享中得到了意义。

    我只能说,当我们通过值传递数组时foreach 是传递正在迭代的数组,但是当通过引用传递时,它采用了原始 $list。问题是:为什么foreach 会传递那个数组?

    【讨论】:

    • + 不错,但你需要看看stackoverflow.com/a/14849560/1226894
    • @Baba 是的,我已经看到并且正在考虑“wtfphp”。
    • 我自己都不敢相信
    • 你知道在函数中运行循环会给出 3 个不同的结果 -3v4l.org/1aUpd 但迭代器给出相同的结果 - 3v4l.org/ViCNn
    • @Baba 不知道。在此之后,我更加确定我们永远不应该在现实世界的应用程序中使用这些结构。以后会很头疼。行为只是未定义的。
    【解决方案5】:

    如果是谎言,您使用的代码。即使从字面上看,它也可能看起来像相同的代码,但变量不是 (http://3v4l.org/jainJ)。

    要回答您的实际问题,请使用正确的工具以获得一致的结果。

    如果你需要一个带有数组值的变量,分配它:

    $list = array(....);
    

    如果你需要获取那个数组的当前值,使用它之前foreach:

    $current = current($list);
    

    因为在 foreach 内部,这可能是相同的变量名称,但值会不同(想象一下,您正在迭代!)。

    如果每次迭代都需要 当前 值,请使用它:

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

    看到$current

    天哪,是的,就是这么简单。等等,我已经有了一致的结果。哦,不自欺欺人就这么容易。耶! ;)

    对于日志:将变量作为函数参数传递使其成为新变量。即使是参考(已解释)。

    如有疑问,请不要使用 PHP 引用。甚至没有变量:http://3v4l.org/6p5nZ

    【讨论】:

    • + 哈哈 .... 不错的解决方法
    【解决方案6】:

    很好的指出。但似乎不同版本的 php 存在内存指向问题。 current 也只给出你没有在任何地方增加(导航)的当前位置,所以没有得到正确的输出。由于不同版本的 php 以不同的方式解释数组的下一个和起点,因此解决方案可能是在循环内有一些条件的 reset。 (顺便说一句,循环然后使用 current, next 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;
      }
      

      应该这样做。

      【讨论】:

      • 请查看问题中所述的问题。问题是关于 current 在 foreach 中的异常行为,而不是如何获得特定的输出。
      【解决方案8】:

      使用这个你已经知道发生了什么!

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

      可能对你有帮助!

      【讨论】:

        猜你喜欢
        • 2021-03-02
        • 1970-01-01
        • 1970-01-01
        • 2013-05-30
        • 2021-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多