通过引用循环在foreach中取消设置的奇怪行为

时间:2014-06-18 13:55:58

标签: php foreach reference

我知道在通过引用循环时不应该修改数组的物理结构,但是我需要解释代码中发生了什么。我们走了:

$x= [[0],[1],[2],[3],[4]];
foreach ($x as $i => &$upper) {
    print $i;
    foreach ($x as $j => &$lower) {

        if($i == 0 && $j == 2) {
            unset($x[2]);
        } else if($i == 1 && $j == 3) {
            unset($x[3]);
        }
    }
}

输出为01。令人惊讶的是,对于索引01,外循环仅迭代两次。我期待输出为014

我已经阅读了很多关于使用数组引用的危险的博客文章和问题,但没有任何东西可以解释这种现象。我现在已经打了好几个小时了。

编辑:

上面的代码是最小的可重现代码。一个似乎是这种情况的解释(但不正确的一个)是这样的:

在内部指针设置为索引2之前,外部循环经历两次迭代。但是循环在索引2找不到任何元素,因此认为没有元素留下并退出。

这个理论的问题是它没有完全解释这个代码:

$x= [[0],[1],[2],[3],[4]];
foreach ($x as $i => &$upper) {
    print $i;
    foreach ($x as $j => &$lower) {

        if($i == 0 && $j == 2) {
            unset($x[2]);
            // No if else here
            unset($x[3]);
        }
    }
}

出于同样的原因,上面的代码也应该生成01,但它的实际输出是014,正如预期的那样。即使删除了一系列中的两个项目,php也知道仍然需要迭代的元素。这可能是php脚本引擎的错误吗?

1 个答案:

答案 0 :(得分:6)

重现问题的简单代码:

$x = [0, 1, 2];
foreach ($x as $k => &$v) {
    print $k;
    if ($k == 0) {
        unset($x[1]);
    }
    end($x); // move IAP to end
    next($x); // move IAP past end (that's the same as foreach ($x as $y) {} would do)
}

如果你对一个数组进行预处理,那么它就会被复制(=迭代时没问题,你将迭代整个原始数组)。 但是,如果您通过引用进行预处理,则数组被复制(引用需要与原始数组匹配,因此无法复制)。

Foreach内部始终保存下一个元素的位置以进行迭代。 但是当删除数组的下一个位置时,foreach需要返回到数组并检查它的内部数组指针(IAP)。

在这种情况下,下一个位置被销毁并且 IAP超过了结束,它结束了循环。

这就是你在这里看到的。


同样有趣:hhvm在这里与php有不同的行为:http://3v4l.org/81rl8


附录:无限的foreach循环:

$x = [0,1,2];
foreach ($x as $k => &$v) {
    print $k;
    if ($k == 1) {
        unset($x[2]);
    } else {
        $x[2] = 1;
    }
    reset($x);
}

如果您理解我上面的解释,请猜出为什么会无限循环。