Php 在foreach()迭代期间修改数组

Php 在foreach()迭代期间修改数组,php,arrays,foreach,Php,Arrays,Foreach,我有几个关于在foreach()循环期间修改数组的问题。在下面的代码中,我遍历了三个包含闭包/回调的数组,并调用每个数组。在迭代过程中,我将一个闭包附加到每个数组的末尾,但是有时候foreach()似乎无法识别数组的大小已更改,因此不会调用附加的闭包 class Foo { private $a1 = array(); private $a2 = array(); public function f() { echo '<pre style

我有几个关于在foreach()循环期间修改数组的问题。在下面的代码中,我遍历了三个包含闭包/回调的数组,并调用每个数组。在迭代过程中,我将一个闭包附加到每个数组的末尾,但是有时候foreach()似乎无法识别数组的大小已更改,因此不会调用附加的闭包

class Foo
{
    private $a1 = array();
    private $a2 = array();

    public function f()
    {
        echo '<pre style="font-size: 20px;">';
        echo 'PHP: ' . phpversion() . '<br><br>';

        $this->a1[] = function() { echo 'a1 '; };
        $this->a1[] = array($this, 'g');
        foreach ($this->a1 as &$v)
        {
            // The callback added in g() never gets called.
            call_user_func($v);
            //echo 'count(v) = ' . count($v) . ' ';
        }

        echo '<br>';

        // The same thing works fine with a for() loop.
        $this->a2[] = function() { echo 'a2 '; };
        $this->a2[] = array($this, 'h');
        for ($i = 0; $i < count($this->a2); ++$i)
            call_user_func($this->a2[$i]);

        echo '<br>';

        // It also works fine using a local array as long as it
        // starts off with more than one element.
        $a3[] = function() { echo 'a3 '; };
        //$a3[] = function() { echo 'a3 '; };
        $i = 0;
        foreach ($a3 as &$x)
        {
            call_user_func($x);
            if ($i++ > 1) // prevent infinite loop
                break;

            // Why does this get called only if $a3 originally starts
            // with more than one element?
            $a3[] = function() { echo 'callback '; };
        }

        echo '</pre>';
    }

    private function g()
    {
        echo 'g() ';
        $this->a1[] = function() { echo 'callback '; };
    }

    private function h()
    {
        echo 'h() ';
        $this->a2[] = function() { echo 'callback '; };
    }
}

$foo = new Foo;
$foo->f();
预期产出:

a1 g() callback
a2 h() callback 
a3 callback
如果我取消对循环前的第二个元素的注释,
$a3
的输出:

a3 a3 callback
  • 为什么第一个循环
    foreach($this->a1 as&$v)
    没有意识到
    $v
    还有另一个元素需要迭代
  • 为什么在foreach($a3 as&$x)的第三个循环中修改
    $a3
    会起作用,但仅当数组从多个元素开始时才起作用
  • 我意识到在迭代过程中修改数组可能不是一个好主意,但由于PHP似乎允许这样做,我很好奇为什么上面的工作方式是这样的

    1.为什么foreach($this->a1 as&$v)的第一个循环没有意识到$v还有另一个元素需要迭代

    这种行为似乎是由于每次foreach迭代时数组上的内部指针被提升所致。在数组的最后一次迭代时,即内部指针已为null时,将数组元素添加到数组的末尾,意味着不会对该元素进行迭代。通过对代码进行一些修改,可以看到这一点

    class Foo
    {
        private $a1 = array();
        private $a2 = array();
    
        public function f()
        {
            echo '<pre style="font-size: 20px;">';
            echo 'PHP: ' . phpversion() . '<br><br>';
    
            $this->a1[] = function() { echo 'a1 <br/>'; };
            $this->a1[] = array($this, 'g');
            foreach ($this->a1 as $key => &$v)
            {
               //lets get the key that the internal pointer is pointing to 
               // before the call.
                      $intPtr = (key($this->a1) === null) ? 'null' : key($this->a1);
                    echo 'array ptr before key ', $key, ' func call is ',    
                           $intPtr, '<br/>' ;
                call_user_func($v);
                //echo 'count(v) = ' . count($v) . ' ';
            }
    
            echo '<br><br>';
    
            // The same thing works fine with a for() loop.
            $this->a2[] = function() { echo 'a2 '; };
            $this->a2[] = array($this, 'h');
            for ($i = 0; $i < count($this->a2); ++$i)
                call_user_func($this->a2[$i]);
    
            echo '<br><br>';
    
            // It also works fine using a local array as long as it
            // starts off with more than one element.
            $a3[] = function() { echo 'a3 '; };
            //$a3[] = function() { echo 'a3 '; };
            $i = 0;
            foreach ($a3 as &$x)
            {
                call_user_func($x);
                if ($i++ > 1) // prevent infinite loop
                    break;
    
                // Why does this get called only if $a3 originally starts
                // with more than one element?
                $a3[] = function() { echo 'callback '; };
            }
    
            echo '</pre>';
        }
    
        private function g()
        {
            echo 'g() <br>';
            $this->a1[] = function() { echo 'callback '; };
        }
    
        private function h()
        {
            echo 'h() <br>';
            $this->a2[] = function() { echo 'callback '; };
        }
    }
    
    $foo = new Foo;
    $foo->f(); 
    
    class-Foo
    {
    private$a1=array();
    private$a2=array();
    公共职能f()
    {
    回声';
    回显“PHP:”.phpversion()。

    ; $this->a1[]=function(){echo'a1
    ';}; $this->a1[]=数组($this,'g'); foreach($this->a1作为$key=>&$v) { //让我们获取内部指针指向的键 //在电话之前。 $intPtr=(key($this->a1)==null)?'null':key($this->a1); echo'array ptr before key',$key,'func call is', $intPtr,“
    ”; 调用用户函数($v); //回声“计数(v)=”。计数($v)。”; } 回音“

    ”; //同样的事情也适用于for()循环。 $this->a2[]=function(){echo'a2';}; $this->a2[]=数组($this,'h'); 对于($i=0;$ia2);++$i) 调用用户函数($this->a2[$i]); 回音“

    ”; //使用本地数组也可以很好地工作,只要 //从多个元素开始。 $a3[]=function(){echo'a3';}; //$a3[]=function(){echo'a3';}; $i=0; 外汇(a3美元和x美元) { 调用用户函数($x); if($i++>1)//防止无限循环 打破 //为什么只有在$a3最初启动时才会调用此函数 //有多个元素? $a3[]=function(){echo'callback';}; } 回声'; } 私有函数g() { 回声“g()
    ”; $this->a1[]=function(){echo'callback';}; } 私有函数h() { 回音“h()
    ”; $this->a2[]=function(){echo'callback';}; } } $foo=新foo; $foo->f();
    输出:

    PHP: 5.3.14-1~dotdeb.0
    
    a1 g() 
    a2 h() callback 
    a3
    
    array ptr before key 0 func call is 1
    a1 
    array ptr before key 1 func call is null <-will not iterate over any added elements!
    g() 
    
    a2 h() 
    callback 
    
    a3
    
    键0之前的数组ptr func调用为1
    a1
    
    键1 func调用之前的数组ptr为null有趣的观察:

    echo "foreach:  ";
    $a = array(1,2,3);
    foreach($a as $v) {
      echo $v, " ";
      if ($v===1) $a[] = 4;
      if ($v===4) $a[] = 5;
    }
    
    echo "\nforeach&: ";
    $a = array(1,2,3);
    foreach($a as &$v) {
      echo $v, " ";
      if ($v===1) $a[] = 4;
      if ($v===4) $a[] = 5;
    }
    
    echo "\nwhile:    ";
    $a = array(1,2,3);
    while(list(,$v) = each($a)) {
      echo $v, " ";
      if ($v===1) $a[] = 4;
      if ($v===4) $a[] = 5;
    }
    
    echo "\nfor:      ";
    $a = array(1,2,3);
    for($v=reset($a); key($a)!==null; $v=next($a)) {
      echo $v, " ";
      if ($v===1) $a[] = 4;
      if ($v===4) $a[] = 5;
    }
    
    导致

    foreach:  1 2 3 
    foreach&: 1 2 3 4 
    while:    1 2 3 4 5 
    for:      1 2 3 4 5 
    
    这意味着:

    • 正常的
      foreach
      循环在数组的副本上运行,循环中对数组的任何修改都不会影响循环
    • 具有引用值的
      foreach
      将强制使用原始数组,但在分配键和值变量后,在每次迭代之前都会提前数组指针。此外,还有一些优化正在进行,以防止在指针到达末尾时进行另一次检查。因此,在最后一次迭代开始时,循环被告知再次运行,然后完成—不可能再发生干扰
    • 使用
      each()
      while
      循环推进数组指针,就像
      foreach
      一样,但在最后一次迭代后再次显式检查它
    • 对于
    循环,数组指针在每次迭代后向前移动,显然在任何时候更改数组都没有问题
    首先,当数组被循环时,您正在修改数组,而PHP的设计实际上并没有考虑到这一点。您最好在循环中使用回调函数,使用foreach而不引用它。@Hiroto手册中提到了它,所以我认为PHP的设计就是考虑到了这一点。然而,我意识到这可能不是一个好的实践,但我仍然想知道为什么它的行为如此,它似乎不一致。我制作了一个快速的类,基本上做你的类所做的,并得出了相同的结果。根据php foreach页面,
    while(list(,$value)=each($arr))
    应该与foreach完全相同,但是我在使用while循环而不是foreach时没有*问题。它提到它是因为它可以完成,但是php的
    foreach
    是为了复制数组并对其进行迭代而设计的,因为你不会以这种方式遇到无限循环。正如它所说:“丑陋的问题往往需要丑陋的解决方案。以纯粹的方式解决丑陋的问题是极其困难的。”。“优雅”的解决方案可能是使用回调函数,而不是依赖数组。