不寻常的PHP数组迭代行为

时间:2018-03-09 21:03:52

标签: php arrays loops

我在迭代数组时遇到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循环中,才会出现这种情况,其中值通过引用使用。数组的长度似乎不会影响行为;最后一个元素总是被倒数第二个元素替换。

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

这是故意还是PHP的错误?

编辑:请参阅上面的重复问题,特别是this article,它可以直观地说明行为的原因。

2 个答案:

答案 0 :(得分: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"
}

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

答案 1 :(得分:0)

我相信foreach ($example as &$letters)设置一个变量$ letters,指向原始数组,以便更改它(而不是为默认行为的迭代制作副本)和下一个语句foreach ($letters as &$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
    }
}