foreach循環時動態往數組裏添加數據
有一次作項目中,foreach的時候須要動態往數組裏添加數據(咱們這裏隨便舉個例子)php
結果:segmentfault
哎?奇了怪了,這說明foreach循環時能夠動態的往數組裏添加數據,爲何$arr的數據確實被添加上了,可是沒有被foreach循環出來呢?
網上查找得知,foreach循環的實際上是數組的拷貝,而不是該數組自己,若是是數組拷貝的話,確定是改變數組以前進行的拷貝,根據運行結果得知
雖然循環中確實改變了原有的數組,但循環的是拷貝的數組(也就是老的數組),因此你沒法循環到新添加的元素
好吧,暫且忍了
若是foreach的時候用引用賦值,新添加的數據就能夠被循環出來了數組
結果:函數
若是說foreach循環的是數組的拷貝,那爲何引用循環的時候新添加的數據又被循環出來了呢?
foreach究竟是循環的數組自己呢仍是循環數組的拷貝
這就不得不引出PHP的zval結構、copy on write機制
http://segmentfault.com/a/1190000004340427
還有一個知識點須要瞭解下,就是PHP數組複製的機制
複製一個數組,就是把一個數組賦值給一個變量即可。會把數組指針位置一同複製。這裏面有兩種狀況:
① 指針位置合法,這時直接複製,無影響
② 原數組指針位置非法時(移出界),「新」數組指針會初始化(這裏的新爲何要加引號?請看下文),而老的數組指針位置不變,仍是false
先看例子:spa
結果:debug
結果:指針
出現這種狀況好像不對?$arr2 難道不是新數組?新數組的數組指針應該重置了啊
這裏注意了:$arr2 = $arr1 ,在倆變量都沒發生寫操做時,他們其實引用的是同一個內存地址。在其中一個變量發生寫操做後,內存地址會複製一份,發生改變的變量會去引用它,並把數組指針初始化。因此 $arr1 會去引用複製的內存地址,並將指針初始化ip
咱們來探討下foreach究竟是怎麼工做的
① foreach循環以前,php會判斷數組的引用計數,若是大於1,PHP則會直接拷貝數組,循環拷貝的數組,保證指向這個zval的其它數組不被foreach破壞內存
結果:get
② 若是數組對應的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什麼結果
結果:
意思是:鍵不能被引用,壓根就沒有這種語法格式