【问题标题】:Unusual PHP array iteration behavior [duplicate]异常的PHP数组迭代行为[重复]
【发布时间】:2018-08-18 12:25:54
【问题描述】:

我在遍历数组时遇到了 PHP 中的异常行为,希望得到解释或确认这是一个错误。

对于初学者,这里是 PHP 版本:

PHP 7.2.3 (cli) (built: Mar  8 2018 10:30:06) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.3, Copyright (c) 1999-2018, by Zend Technologies

这是我为复制行为而创建的示例脚本:

<?php
$example = [
    [
        'a' => 'A',
        'b' => 'B',
    ],
    [
        'c' => 'C',
        'd' => 'D',
    ],
    [
        'e' => 'E',
        'f' => 'F'
    ]
];

foreach ($example as &$letters) {
    foreach ($letters as &$letter) {
        // Do nothing
    }
}

print_r($example);

foreach ($example as $letters) {
    print_r($letters);
}
?>

这是输出:

Array
(
    [0] => Array
        (
            [a] => A
            [b] => B
        )

    [1] => Array
        (
            [c] => C
            [d] => D
        )

    [2] => Array
        (
            [e] => E
            [f] => F
        )

)
Array
(
    [a] => A
    [b] => B
)
Array
(
    [c] => C
    [d] => D
)
Array
(
    [c] => C
    [d] => D
)

意外的行为是倒数第二个元素仅在数组的第二次迭代期间替换最后一个元素。但是,在打印整个数组时看不到这一点。

只有在嵌套的foreach 循环中,值被引用使用时才会发生这种情况。数组的长度似乎不会影响行为;最后一个元素总是替换为倒数第二个元素。

此外,如果在最后一个 foreach 循环之前添加 unset($letters); 语句,则问题似乎已解决。

这是故意的,还是 PHP 的错误?

编辑:请参阅上面的重复问题,特别是 this article,它直观地展示了为什么行为是这样的。

【问题讨论】:

  • 您必须取消设置引用以避免该行为。如果我没记错的话,它在 foreach 的文档中。
  • php.net/manual/en/control-structures.foreach.php 检查粉红色的警告部分。
  • this question 的答案有一个链接,指向一篇说明该行为的文章。不过,它不被 PHP 视为错误。
  • 阅读 PHP references.
  • 我将其添加到此处的重复列表中。我不需要重新发明轮子。 :) 你知道,我说过“PHP 不认为它是一个错误”,但它真的不是一个错误。这只是使用这样的引用的非直观结果。

标签: php arrays loops


【解决方案1】:

因为您留下的参考资料只是躺在那里等着破坏。您需要在循环后unset() 他们以避免这种情况。

foreach ($example as &$letters) {
    foreach ($letters as &$letter) {
        // Do nothing
    }
    unset($letter);
}
unset($letters);

此外,您应该使用var_dump() 而不是print_r(),因为它会显示更多有用的信息,为您提供解决此问题和许多其他问题的线索。

例如:您上面的损坏输出如下所示:

array(3) {
  [0]=>
  array(2) {
    ["a"]=>
    string(1) "A"
    ["b"]=>
    string(1) "B"
  }
  [1]=>
  array(2) {
    ["c"]=>
    string(1) "C"
    ["d"]=>
    string(1) "D"
  }
  [2]=>
  &array(2) {
    ["e"]=>
    string(1) "E"
    ["f"]=>
    &string(1) "F"
  }
}
array(2) {
  ["a"]=>
  string(1) "A"
  ["b"]=>
  string(1) "B"
}
array(2) {
  ["c"]=>
  string(1) "C"
  ["d"]=>
  string(1) "D"
}
array(2) {
  ["c"]=>
  string(1) "C"
  ["d"]=>
  string(1) "D"
}

这表明数组和子数组中的最后一个元素仍然是引用,这会导致后续循环出现问题。

【讨论】:

  • 我不是在寻求修复;相反,我希望了解为什么会发生这种情况。
【解决方案2】:

我相信foreach ($example as &amp;$letters) 设置了一个指向原始数组的变量 $letters 以更改它(而不是为迭代制作副本,这是默认行为),并且您的下一条语句 foreach ($letters as &amp;$letter) 修改了数组,因为它修改了 $letters。

删除我这里的&符号:

$example = [
    [
    'a' => 'A',
    'b' => 'B',
    ],
    [
    'c' => 'C',
    'd' => 'D',
    ],
    [
    'e' => 'E',
    'f' => 'F'
    ]
];

foreach ($example as $letters) {
    foreach ($letters as $letter) {
    // Do nothing
    }
}

print_r($example);

foreach ($example as $letters) {
    print_r($letters);
}

这将是你的输出:

Array
(
    [0] => Array
    (
        [a] => A
        [b] => B
    )

    [1] => Array
    (
        [c] => C
        [d] => D
    )

    [2] => Array
    (
        [e] => E
        [f] => F
    )

)
Array
(
    [a] => A
    [b] => B
)
Array
(
    [c] => C
    [d] => D
)
Array
(
    [e] => E
    [f] => F
)

请注意,如果您绝对必须使用与号,因为您确实想要对原始数组进行更改,那么只需删除一个与号就会产生相同的输出:

foreach ($example as $letters) { // removed the ampersand here
    foreach ($letters as &$letter) {
        // Do nothing
    }
}

【讨论】:

  • 抱歉,不是要更改/修复初始循环,而是要解释为什么行为是处理引用时的方式。
  • @WesCossick 听起来你不明白assigning a variable by reference 做了什么。正是您对与号的使用(这会导致通过引用分配变量)导致原始数组被更改。你的代码很粗心。
  • 我认为您假设这个问题正在寻求修复某种形式的损坏代码。事实并非如此。我不只是“不小心”写了一个嵌套的foreach 循环,它使用什么都不做的引用。它是专门为这个问题构建的,以创建特定的行为。问题是寻求解释为什么会发生这种行为,而不是如何完全回避这种行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-17
  • 2023-01-14
  • 1970-01-01
  • 1970-01-01
  • 2017-09-28
相关资源
最近更新 更多