foreach循环时动态往数组里添加数据
有一次做项目中,foreach的时候需要动态往数组里添加数据(我们这里随便举个例子)
结果:
哎?奇了怪了,这说明foreach循环时可以动态的往数组里添加数据,为什么$arr的数据确实被添加上了,但是没有被foreach循环出来呢?
网上查找得知,foreach循环的其实是数组的拷贝,而不是该数组本身,如果是数组拷贝的话,肯定是改变数组之前进行的拷贝,根据运行结果得知
虽然循环中确实改变了原有的数组,但循环的是拷贝的数组(也就是老的数组),所以你无法循环到新添加的元素
好吧,暂且忍了
如果foreach的时候用引用赋值,新添加的数据就可以被循环出来了
结果:
如果说foreach循环的是数组的拷贝,那为什么引用循环的时候新添加的数据又被循环出来了呢?
foreach到底是循环的数组本身呢还是循环数组的拷贝
这就不得不引出PHP的zval结构、copy on write机制
http://segmentfault.com/a/1190000004340427
还有一个知识点需要了解下,就是PHP数组复制的机制
复制一个数组,就是把一个数组赋值给一个变量便可。会把数组指针位置一同复制。这里面有两种情况:
① 指针位置合法,这时直接复制,无影响
② 原数组指针位置非法时(移出界),“新”数组指针会初始化(这里的新为什么要加引号?请看下文),而老的数组指针位置不变,还是false
先看例子:
结果:
结果:
出现这种情况好像不对?$arr2 难道不是新数组?新数组的数组指针应该重置了啊
这里注意了:$arr2 = $arr1 ,在俩变量都没发生写操作时,他们其实引用的是同一个内存地址。在其中一个变量发生写操作后,内存地址会复制一份,发生改变的变量会去引用它,并把数组指针初始化。所以 $arr1 会去引用复制的内存地址,并将指针初始化
我们来探讨下foreach到底是怎么工作的
① foreach循环之前,php会判断数组的引用计数,如果大于1,PHP则会直接拷贝数组,循环拷贝的数组,保证指向这个zval的其它数组不被foreach破坏
结果:
② 如果数组对应的zval引用计数为1,foreach一开始,数组的引用计数就+1,当数组改变时,变量分离、发生拷贝(COW)
结果:
这个3是怎么得出来的呢?
数组本身refcount为1,foreach的时候+1,调用debug_zval_dump函数的时候又+1
结果:
修改数组的时候变量分离,发生拷贝
③ 如果数组对应的zval的is_ref为1,则不会发生拷贝,直接循环原数组,上面两条规则作废,因为这里,数组的任何变动都必须体现在引用上,包括数组内部指针的变动,如果这里还是拷贝了数组,就会打破引用机制
结果:
所以foreach循环中,这两种情况也可以将数组的改变在循环中体现出来
结果:
此时循环用的$arr是直接指向原数组的,而不是copy了一份
如果定义了$arr为全局变量的话,$arr也会变成引用,因为global $arr等价于$arr=&$arr;
结果同上
④ 如果foreach的时候,$v是引用,即foreach($arr as $k=>&$v){},zend最终会将数组对应的zval的is_ref设为1,接着按上一条规则处理,foreach循环完,又将数组zval的is_ref置为0
结果:
哦,这么一下子就明白了,数组foreach的时候动态往里添加数据
起初foreach循环的是数组本身(只是数组zval的refcount+1),直接往数组里添加数据的时候(非引用),发生了写入操作,此时发生了一个数组拷贝的动作,被拷贝的数组和拷贝出来的新数组的值、指针位置都是一样的(拷贝的时候连同数组的指针位置一同拷贝过去),内存开辟出新的空间存放被改变的数组,也就是例子中的$arr,而另一个数组是看不见,摸不着的(我们用$tmp来代替该数组),PHP继续循环看不见的数组$tmp(这样能保证数组循环不被破坏),所以上面的例子(非引用)确实给原数组增加上数据了,但是却没有被循环出来
而foreach($arr as &$v){···}的时候,此方法将以引用赋值而不是拷贝一个值,$v和$arr[$k]指向同一内存地址,此时foreach循环的是原数组,数组的指针也是在原数组中移动的,所以新添加的数据可以被循环出来,值的变化也直接影响数组本身的值
那既然&的时候,foreach直接循环的是原数组,那我这样呢?
结果:
既然&的时候,直接操作的是原数组,为什么unset($v)之后,原数组不变呢?
foreach($arr as &$v){···}的时候,相等于$v=&$arr[$k]
$arr[$k]和$v同时指向$arr[$k]的内存地址,即便是unset($v),仅仅是删除了$v对内存空间的引用,并没有删除$arr[$k]对内存地址的引用,所以$arr[$k]依然健在,$arr自然也就没变化,所以应该这样
结果:
还有一点需要注意:&$k什么结果
结果:
意思是:键不能被引用,压根就没有这种语法格式
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。