foreach循环中current()的意外行为

时间:2013-02-13 08:00:49

标签: php arrays loops foreach

这是一个简单的循环

$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

8 个答案:

答案 0 :(得分:18)

为什么以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操作期间从引用数组变量获取一致值。

答案 1 :(得分:3)

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

在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(WOW! B 缺失)

示例:4

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

$refcopy = &$list;

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

输出:BCD

无法准确确定foreach循环内部会发生什么!!!

答案 2 :(得分:2)

嗯,我没有真正的线索,为什么会这样,但我怀疑它可能与如何评估/处理任务有关。为了好玩,我尝试了这个并导致了另一个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

中可能值得注意这一行
  

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

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

答案 3 :(得分:1)

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

由于我在这里找不到这个问题的答案,我会(尝试)解释。

foreach $list的第一次迭代未实际复制之前。只有$list的{​​{1}}才会增加到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);
}

得到以下输出:

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)。在第二个示例中(按值传递)refCount增加到3,因为$list被复制用于函数。在第三个示例中,计数保持为1,因为$list是按值传递的。我需要一些时间来实现原因。如果你从这个结果份额中得到了重点。

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

答案 4 :(得分:1)

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

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

如果需要带有数组值的变量,请指定它:

$list = array(....);

如果您需要获取 数组的当前值,请在 foreach之前使用

$current = current($list);

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

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

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

请参阅$current

哦,天啊,是的,就这么简单。等等我已经有了一致的结果。哦,不容易欺骗自己很容易。好极了! ;)

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

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

答案 5 :(得分:0)

很好的指出。但似乎内存指向不同版本的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

答案 6 :(得分:-1)

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

应该这样做。

答案 7 :(得分:-1)

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

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

可能会帮助你!