45

这是一个简单的循环

$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应该得到但没有得到AAAAPHP

注意*我知道我可以简单地使用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输出?
  • 多余的“A”从何而来?

更新 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

问题 :

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

8 回答 8

21

为什么以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_FREEforeach.

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

于 2013-02-13T09:05:48.527 回答
3

来自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.

前锋


编辑:我还想分享我新的令人困惑的发现!!!

示例 1:

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

输出:AAA

示例 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 循环中会发生什么!!!

于 2013-02-13T08:11:44.593 回答
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中的这一行可能值得注意:

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

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

于 2013-02-13T08:46:27.620 回答
1

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

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

在第一次迭代之前foreach $list实际上并没有复制。只有的引用计数$list增加到 2。所以在第一次迭代中:第一个值$list将被复制到 中$var,指针将移动到的第二个元素,$list并进行实际的复制$list因此,当您调用current指向第二个元素的指针但在第二次和更远的迭代中它永远不会被修改,因为存在的实际副本总是会输出第二个元素。$listcurrent

编辑:

我玩过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传递那个数组?

于 2013-02-13T08:33:48.893 回答
1

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

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

如果您需要一个具有数组值的变量,请分配它:

$list = array(....);

如果您需要获取该数组的当前值,请以下之前使用foreach它:

$current = current($list);

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

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

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

看到了$current吗?

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

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

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

于 2013-02-13T12:48:03.770 回答
0

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

于 2013-02-13T08:49:00.150 回答
-1
$list = array("A", "B", "C","D");
foreach ($list as $var) {
    echo $var;
}

应该这样做。

于 2013-02-13T08:03:38.480 回答
-1

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

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

可能是它帮助你!

于 2013-02-13T10:32:39.213 回答