看過PHP相關(guān)書籍的都會(huì)了解到PHP有個(gè)這樣的特性:寫時(shí)復(fù)制,所以在用foreach時(shí),需要對(duì)數(shù)據(jù)做修改的時(shí)候,都會(huì)復(fù)制數(shù)據(jù),如果數(shù)據(jù)很大,那么就會(huì)帶來一定的內(nèi)存消耗,所以為了避免這種復(fù)制操作,就用到了引用,下面就介紹下引用的坑
問題案例:
- <?php
- $arr = array(4, 5, 6);
- var_dump($arr);
- foreach ($arr as &$v) {
- //do something here
- }
- foreach ($arr as $v) {
- //do something here
- }
- var_dump($arr);
- ?>
輸出為:
- array(3) {
- [0]=>
- int(4)
- [1]=>
- int(5)
- [2]=>
- int(6)
- }
- array(3) {
- [0]=>
- int(4)
- [1]=>
- int(5)
- [2]=>
- &int(5)
- }
問題分析:
foreach 中不使用引用就沒事, 用 foreach $k => $v 然后 $ar[$k] 來改變?cè)紨?shù)組, 略微損失點(diǎn)效率。
執(zhí)行第一個(gè)使用引用的 foreach 時(shí):
一開始,$v 指向 $arr[0] 的存儲(chǔ)空間,空間內(nèi)存儲(chǔ)著 4,foreach 結(jié)束時(shí),$v 指向 $arr[2] 的存儲(chǔ)空間,空間內(nèi)存儲(chǔ)著 6 。
開始執(zhí)行第二個(gè) foreach 時(shí):
注意和第一個(gè) foreach 不同, 第二個(gè) foreach 沒有使用引用,那么就是賦值方式,即將 $arr 的值依次 賦值 給 $v,進(jìn)行到第一個(gè)元素時(shí),要將 $ar[0] 賦值給 $v,問題就在這里,由于剛剛執(zhí)行完第一個(gè) foreach,$v 不是一個(gè)新變量,而是已經(jīng)存在的、指向 $arr[2] 的那個(gè) 引用 ,如此一來,對(duì) $v 進(jìn)行賦值的時(shí)候,就將 $arr[0] = 4 寫入了 $arr[2] 的實(shí)際存儲(chǔ)空間,相當(dāng)于對(duì) $arr[2] 進(jìn)行賦值,依此類推,第二個(gè) foreach 執(zhí)行的結(jié)果,就是數(shù)組的最后一個(gè)元素變成了倒數(shù)第二個(gè)元素的值。
PHP 的開發(fā)者也認(rèn)為,這種情況屬于語言特性造成的,不是 bug。要修復(fù)這個(gè)問題,一種方法是對(duì) foreach 進(jìn)行特殊處理,另一種就是限制 foreach 中 $v 的作用域, 這兩種方式都與目前 PHP 的語言特性不符,開發(fā)人員不愿改,但還是在 官方文檔 中用 Warning 進(jìn)行了說明。
解決方案:
簡(jiǎn)單的方法,就是在使用了引用的 foreach 之后,unset 掉 $v
修改后的案例:
- <?php
- $arr = array(4, 5, 6);
- var_dump($arr);
- foreach ($arr as &$v) {
- //do something here
- }
- unset($v);
- foreach ($arr as $v) {
- //do something here
- }
- var_dump($arr);
- ?>
輸出:
- array(3) {
- [0]=>
- int(4)
- [1]=>
- int(5)
- [2]=>
- int(6)
- }
- array(3) {
- [0]=>
- int(4)
- [1]=>
- int(5)
- [2]=>
- int(6)
- }
補(bǔ)充:
foreach雖然簡(jiǎn)單,不過它可能會(huì)出現(xiàn)一些意外的行為,特別是代碼涉及引用的情況下。
下面列舉了幾種case,有助于我們進(jìn)一步認(rèn)清foreach的本質(zhì):
- $arr = array(1,2,3);
- foreach($arr as $k => &$v) {
- $v = $v * 2;
- }
- // now $arr is array(2, 4, 6)
- foreach($arr as $k => $v) {
- echo "$k", " => ", "$v";
- }
為何不是0=>2 1=>4 2=>6 ?
其實(shí),我們可以認(rèn)為 foreach($arr as $k => $v) 結(jié)構(gòu)隱含了如下操作,分別將數(shù)組當(dāng)前的'鍵'和當(dāng)前的'值'賦給變量$k和$v。具體展開形如:
- foreach($arr as $k => $v){
- //在用戶代碼執(zhí)行之前隱含了2個(gè)賦值操作
- $v = currentVal();
- $k = currentKey();
- //繼續(xù)運(yùn)行用戶代碼
- ……
- }
根據(jù)上述理論,現(xiàn)在我們重新來分析下第一個(gè)foreach:
第1遍循環(huán),由于$v是一個(gè)引用,因此$v = &$arr[0],$v=$v*2相當(dāng)于$arr[0]*2,因此$arr變成2,2,3
第2遍循環(huán),$v = &$arr[1],$arr變成2,4,3
第3遍循環(huán),$v = &$arr[2],$arr變成2,4,6
隨后代碼進(jìn)入了第二個(gè)foreach:
第1遍循環(huán),隱含操作$v=$arr[0]被觸發(fā),由于此時(shí)$v仍然是$arr[2]的引用,即相當(dāng)于$arr[2]=$arr[0],$arr變成2,4,2
第2遍循環(huán),$v=$arr[1],即$arr[2]=$arr[1],$arr變成2,4,4
第3遍循環(huán),$v=$arr[2],即$arr[2]=$arr[2],$arr變成2,4,4
OK,分析完畢。
如何解決類似問題呢?php手冊(cè)上有一段提醒:
Warning : 數(shù)組最后一個(gè)元素的 $value 引用在 foreach 循環(huán)之后仍會(huì)保留。建議使用unset()來將其銷毀。
- $arr = array(1,2,3);
- foreach($arr as $k => &$v) {
- $v = $v * 2;
- }
- unset($v);
- foreach($arr as $k => $v) {
- echo "$k", " => ", "$v";
- }
- // 輸出 0=>2 1=>4 2=>6
從這個(gè)問題中我們可以看出,引用很有可能會(huì)伴隨副作用。如果不希望無意識(shí)的修改導(dǎo)致數(shù)組內(nèi)容變更,最好及時(shí)unset掉這些引用。
unset只會(huì)刪除變量,并不會(huì)清空變量值對(duì)應(yīng)的內(nèi)存空間:這是與指針不同的地方,如下:
- $a = "str";
- $b = &$a;
- unset($b);
- echo $a;
依然輸出 str
新聞熱點(diǎn)
疑難解答