【问题标题】:Strange behavior of unset in foreach by reference loop通过引用循环在 foreach 中未设置的奇怪行为
【发布时间】:2014-08-08 20:44:43
【问题描述】:

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

$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 脚本引擎的错误吗?

【问题讨论】:

  • 那你期待什么?
  • 添加一些调试输出。将var_dump($x) 放入代码的各个部分,您将确切地看到数组发生了什么。
  • 问题是您在 foreach 期间取消设置值并导致 php 中断,因为在其循环期间正在修改 foreach。我会重新考虑你的逻辑,要么复制你取消设置的数组,要么尝试不同的东西。
  • @nl-x 查看我的编辑。到目前为止,我们讨论过的任何理论都无法解释这个案例。
  • @juzerali 我同意你的看法。奇怪

标签: php foreach reference


【解决方案1】:

重现问题的简单代码:

$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,则数组不会被复制(引用需要与原始数组匹配,因此无法复制)。

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

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

【讨论】:

  • 所以你说 foreach 使用它自己的指针,当那个指针失败时它使用数组内部指针。但后来我同意,通过 OP 的编辑,'4' 也不应该打印出来,但它确实......
  • @nl-x 你的问题是哪一个?在 $i == 0 处删除 2 和 3?好吧,您不会删除要迭代的下一个条目,而是稍后删除一个,所以没问题。重要的位置总是只有下一个,其他的都是从(在这种情况下不是复制的)数组中重新获取的。
  • 如果我按照你的逻辑,当我“在 i0j2 删除 2 和 3”时,外部 foreach 应该完成 0,经过 1 并遇到丢失的“2”。然后查看内部数组指针,看看它在数组的末尾(前一个循环(1)将其关闭的地方)
  • @nl-x 没有。 foreach 存储 1 作为下一个键。打印 0。将 4 存储为下一个键。打印 1。将 5 存储为下一个键。打印 4。看到数组的结尾。打印 5. 退出循环。当存储的密钥在它想要获取下一个值的点丢失时,这只是一个问题。
  • 你说的是 5 ,但你的意思是 2 ?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-11
  • 2020-07-12
  • 2014-01-15
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
相关资源
最近更新 更多