PHP中基础中的三大坑,foreach遍历,引用机制&,数组。php
今天咱们在讲讲foreach中的一些奇怪现象。segmentfault
在讲解以前,能够先看看我其余相关的文章,属于同一个大的知识点,都看看有助于理解。数组
当咱们使用foreach时,内部究竟发生了什么?(PHP5)函数
△△△写前声明:如下结论都基于PHP5版本,由于时代在进步,在PHP7中内部的结构体模块和引用模块均发生重大变化,PHP7的foreach输出规则也旋即发生变化。固然,因为PHP7想要普及还须要一到两年(今年是2016)时间,因此这篇文章仍是有些价值的,至少可让你先理解一下PHP内部实现。指针
有疑问能够直接在评论中抛出,我会一一解答。code
本文适合有必定基础的PHPer。blog
那么开始上图,这是鸟哥(惠新宸,PHP7核心开发组成员,开发组中惟一的一个中国人[撒花])在Think 2015 PHP技术峰会上的一个演讲截图,他在讲述PHP5的foreach和PHP7的foreach区别,咱们把他演讲中说起到PHP5的部分拿出来看看。开发
那么咱们着重看下这幅图的三段代码执行流程。
我讲讲三段代码的运行原理
code1.php
<?php $a = array(1,2,3); foreach ($a as $key => $value) { var_dump(current($a)); //output int(2) int(2) int(2) } ?>
输出值为: int(2) int(2) int(2)。
同窗们可能纳闷了,乍一看并无发生明显的写时复制(相关文章)或者强制分裂(相关文章),怎么会是三个'2'呢。
关键点在于current()函数上:
foreach循环开始,拷贝一个数组出来,而后refcount_gc=2(foreach原理不太了解的同窗,能够看看个人另外一篇文章:当咱们使用foreach时,内部究竟发生了什么?(PHP5) )
此时原数组($a)和拷贝数组(我这里命名为$a_copy)的指针均指向下标1,
随后进入大括号执行体,current()操做的参数必须是引用数组(红线部分),若是不是引用数组的话会强制转换成引用数组(即结构体中is_ref__gc从0 -> 1)。
根据强制分裂原理,一个结构体的is_ref__gc的值从0 -> 1的时候,若是refcount_gc=2时,就会发生url"强制分裂了"。
强制分裂后, 原数组($a)和拷贝数组(我这里命名为$a_copy)结构体已经不同,可是foreach操做的是拷贝数组($a_copy),原数组被丢在半道上了,因此三次输出var_dump(current($a))均为2
code2.php
<?php $a = array(1,2,3); $b = &$a; // 结构体中:refcount_gc=2;is_ref_gc=1; foreach ($a as $value) { var_dump(current($a)); //output: int(2) int(3) bool(false) } ?>
这段代码和code1.php原理差很少,只是在foreach前进行了一次引用赋值,结构体变化成:refcount_gc=2;is_ref_gc=1; 随后foreach遍历数组,此时原数组($a),拷贝数组(我这里命名为$a_copy)和$b的指针均指向下标1而且均为引用数负责(is_ref_gc)。
接着进入大括号执行体:var_dump(current($a)); 前面说道",current()操做的参数必须是引用数组(红线部分),若是不是引用数组的话会强制转换成引用数组(即结构体中is_ref__gc从0 -> 1)。"
此处参数$a已经为引用数组,不会发生强制分裂,原数组($a)和拷贝数组($a_copy)为同一个结构体,正常输入为:int(2) int(3) bool(false)
code3.php
<?php $a = array(1,2,3); $b = $a; // 结构体中:refcount_gc=2 foreach ($a as $key => $value) { var_dump(current($a)); //output int(1) int(1) int(1) } ?>
第三段代码和前面两个又有所不一样了,此次$b=$a是使用传值赋值,那么此处为何为3个int(1)呢?此次的缘由在于foreach的机制:
foreach循环以前,须要判断数组的 refcount计数,若是大于1,拷贝数组本身成为一个新的结构体,循环过程操做的是新的结构体。
运行流程: line:3; // $a,$b共用一个结构体 line:4; //foreach开始遍历数组,进行refcount判断,拷贝数组是一个新的结构体,foreach操做的是新的结构体。那么遍历数组时,全程与原数组无关。 Line:5: //打印数组当前单元,因为原数组和拷贝数组已不是一个结构体,因此原数组的指针没变过,打印出int(1)。
若是仍是有些不明觉厉的话,能够反复看多几遍,对了,再次推荐看下这几篇文章,都能理解的话对foreach掌握也差很少够用了。