当正在迭代的数组/集合在循环内被修改时,foreach控制结构如何表现?


How does the foreach control structure behave when the array/collection being iterated is modified inside the loop?

我编写了以下函数来平整化数组:

function flatten() {
    $args  = func_get_args();
    $items = array();
    for ($i = 0; $i < count($args); $i++) {  // <-- (*)
        $arg =& $args[$i];
        if (is_array($arg))
            foreach ($arg as &$item)
                $args[] =& $item;
        else
            $items[] = $arg;
    }
    return $items;
}

我想用简单的foreach ($args as &$arg)代替for行。依据是什么?我曾经写过一个实现Iterator接口的类,它本质上是foreach控制结构工作的基础。如果我没记错的话,foreach控制结构的作用如下:

  1. 使用rewind()方法设置内部索引变量为第一个元素的位置。
  2. 使用valid()方法来测试是否已经到达数组的末尾。如果是,退出。
  3. 使用' Iterator接口的key()current()方法来检索数组当前元素的键和值。
  4. 使用next()方法将内部索引变量设置为紧跟当前元素的位置。
  5. Goto 2。

至少,这就是用户定义类的工作方式。我不太确定它如何与内置数组类型一起工作。它会以同样的方式工作吗?我可以用foreach代替for行吗

首先,我应该指出,一般来说,在迭代集合时修改集合的内容被认为是一件"不好的事情"——如果您尝试这样做,许多语言都会抛出异常。然而,PHP不是这些语言之一。

这里有两件事与你的问题相关:

首先,当使用数组时,PHP的foreach生成数组的副本并在其上迭代。在这种情况下,您可以安全地修改原始数组,但您的foreach不会看到任何更改。在您的例子中,这不起作用——您附加到$args末尾的新值不会在foreach中出现。

可以通过迭代数组的引用来强制PHP使用原始数组。在这种情况下,内部行为将变得相关。PHP有一个指向"下一个"数组元素的内部指针。如果您更改了'foreach'已经看到的数组元素的内容,则不会看到更改。如果在当前元素之外的某个地方更改了数组的内容,就会看到这些更改。这个应该为你工作,但我不知道我是否相信它。

for和foreach是可以互换的,但是在这种情况下,您不能将元素添加到args数组中并立即使用foreach处理它们,因此如果使用foreach,您的函数将无法以相同的方式运行。

关于代码有几点需要指出:1)将count($args)放在for循环的第二个参数中意味着它在循环的每次迭代中都被处理,如果你有一个非常大的数组,这可能会很昂贵。

我会在处理循环之前计算args的数量,将其存储在一个变量中并使用它来代替for参数中的count($args),然后每次向args数组中添加新元素时将计数加1。这会更快,使用更少的内存。

2)这可以通过函数使用递归来清理,这样可以在没有多个循环的情况下完成相同的工作,并且使用的代码会稍微少一些。
$nums = array(1, 2, 3, 4);
$newNum = max($nums) + 1;
foreach ($nums as $num) {
    echo $num;
    if ($newNum > 10) {
        break;
    }
    $nums[] = $newNum++;
}
print_r($nums);
/* output
1234
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
)
*/

使用foreach ($nums as &$num):

/* output
1234567
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
    [9] => 10
)
*/