PHP引用理解之神奇的foreach面试题

最近面试了一些各种背景的同学,发现下面这题鲜有人答出来。题目看起来刁钻,实际上是对引用很好的考察。

一、题目和答案
题目如下(要求写出两次输出的结果):

    $value = ['foo', 'bar', 'cat'];
    foreach($value as &$val) {
        // nothing to do
    }

    var_dump($value);

    foreach($value as $val) {
         // also nothing to do
    }

    var_dump($value);
// the end of the script

如果你不确定,可以直接告诉你答案,如下:

array(3) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  &string(3) "cat"
}
array(3) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  &string(3) "bar"
}

如果有兴趣,建议暂停,自己揣摩下原因。
然后继续往下

二、理论解答

0、先说引用
我理解PHP中的引用是不同的变量名访问同一个变量内容,或者说指向同一块内存地址。
可以类比Linux中的软连接来理解

ln -s 源文件 目标文件

1、再说数组
如果你通读过PHP官方手册foreach章节的话,应该很熟悉,PHP foreach遍历数组是通过数组内部指针移动来实现,foreach开始时数组内部指针会自动指向第一个单元,每次循环中当前单元的值会赋给as后面的变量,同时数组内部指针会往前移动一步。

Warning 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁。

    $value = ['foo', 'bar', 'cat'];
    foreach($value as &$val) {
        // nothing to do
    }
    $val = 'salmonl';
    var_dump($value);

所以上面的代码会输入下面的内容

array(3) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  &string(7) "salmonl"
}

上面这句话太重要了(来自手册后面的评论留言)
“Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().”

I cannot stress this point of the documentation enough!!!

三、实际解惑
理解了上面这一步,这一题基本理解了60%。这道题神奇的地方在于,两次foreach循环使用了同一个数组变量。所以在第二次foreach的时候,$val一直改表着整个数组
第二次foreach循环三次
0、第一次循环借宿
$value = [‘foo’, ‘bar’, ‘foo’];
1、第二次
$value = [‘foo’, ‘bar’, ‘bar’];
这一步对理解尤其关键,因为$value[2] = ‘bar’, 所以数组内部指针移动到第三个单元的时候,内容已经是’bar’了
2、第三次
$value = [‘foo’, ‘bar’, ‘bar’];

四、思考
在以往的工作经历中,发现身边的很多同事对引用掌握的不牢,使用引用导致了很多动态问题。反思了下注意有2点原因
0、对引用理解不深入,不参考手册说明开发。手册明确说了需要unset()。
1、定义变量名的随意性。foreach的时候不加区分的用$k => $v,很容易埋下祸患。

这个问题的解决办法很多,参考
0、第一次foreach后unset($val)。
1、as后面不都使用$val。

五、参考
如果还是一头雾水,推荐读读这篇详细的分析文章《对数组两次foreach的使用陷阱》,写的是真的好。

发表评论

电子邮件地址不会被公开。 必填项已用*标注