謹(jǐn)慎使用PHP的引用原因分析
引用類(lèi)型(Reference)在許多計(jì)算機(jī)語(yǔ)言中都被使用,而且是作為一個(gè)非常強(qiáng)大而實(shí)用的特性存在。它有類(lèi)似指針(Pointer)的實(shí)現(xiàn),卻又有不同于指針的表現(xiàn)。例如C++的引用,可以讓不同變量指向同一個(gè)對(duì)象,同時(shí)又保有直接使用dot來(lái)獲取對(duì)象成員,不用繁瑣的使用dereference運(yùn)算符(*)和Pointer to Member運(yùn)算符(->)。Java和C#中就直接以引用為主要類(lèi)型,盡量讓開(kāi)發(fā)人員避免使用指針。
PHP中也引入了引用類(lèi)型,在對(duì)對(duì)象賦值傳遞上,基本可視為是同于Java/C#的引用傳遞(具體請(qǐng)見(jiàn)Objects and references)。但同時(shí)又支持在基礎(chǔ)類(lèi)型上通過(guò)引用運(yùn)算符(&)來(lái)獲得內(nèi)容的引用。不過(guò)在實(shí)際的使用中,PHP的引用類(lèi)型因?yàn)檎麄€(gè)PHP設(shè)計(jì)結(jié)構(gòu)而存在著許多的問(wèn)題,使得在程序出現(xiàn)非預(yù)計(jì)的結(jié)果。
引用變量可被賦予新的引用
在C++中,引用類(lèi)型的變量只能在其定義時(shí)被賦予引用值,所以我們只要追蹤到變量的定義處就可以知道變量是在操作哪個(gè)內(nèi)容。
但是PHP不同,PHP里模糊了變量的定義,可以不定義就使用的變量。所以可以讓變量被多次賦予引用值。
$x = 21;
$y = 7;
$z = &$x;
$z = &$y;
var_dump($x,$y,$z);
初次看起來(lái),讓人的感覺(jué)是$z變成了對(duì)$x的引用,然后讓$z的內(nèi)容變成了對(duì)$y的引用,也就是說(shuō)$x和$z都成對(duì)$y的引用。但是實(shí)際輸出結(jié)果是:
int(21)
int(7)
int(7)
從結(jié)果上看出,$x保持不變,只是$z被改變成了對(duì)$y的引用。相當(dāng)于先unset了$z變量然后賦予了新值。
$z = &$x;
unset($z);
$z = &$y;
這其實(shí)是比較合理邏輯,就比如下邊的代碼,我們并不是得到類(lèi)似于“指向指針的指針(Pointer point to a Pointer)”那樣的“引用引用的引用(Reference refer to a Referenece)”,只是多個(gè)引用到同一塊內(nèi)容的引用變量。
$x = 21;
$y = &$x;
$z = &$y
引用數(shù)組元素會(huì)讓該元素變成引用類(lèi)型
對(duì)于變量上取引用,并不會(huì)造成原變量類(lèi)型的改變,但是如果取的是數(shù)組中的元素,卻會(huì)讓該元素也變成引用類(lèi)型。
在看問(wèn)題代碼前,首先要指出的是:
Array assignment always involves value copying. Use the reference operator to copy an array by reference.
也就是說(shuō)PHP的數(shù)組賦值是copy而非引用,賦值過(guò)程會(huì)創(chuàng)建新的數(shù)組賦予被賦值的變量。在新變量上的數(shù)組操作并不會(huì)影響到原數(shù)組變量中的內(nèi)容。
$a = array(21, 7);
$b = $a;
$b[0] = 7;
var_dump($a);
echo '<br/>';
var_dump($b);
//Output:
//array(2) { [0]=> int(21) [1]=> int(7) }
//array(2) { [0]=> int(7) [1]=> int(7) }
下邊我們?cè)賮?lái)看看如果引用數(shù)組中的元素,會(huì)有什么異常。
$a = array(21, 7);
$c = & $a[0];
$b = $a;
$b[0]= "21";
$b[1]= "7";
var_dump($a);
echo '<br/>';
var_dump($b);
echo '<br/>';
var_dump($c);
echo '<br/>';
// Output:
// array(2) { [0]=> &string(2) "21" [1]=> int(7) }
// array(2) { [0]=> &string(2) "21" [1]=> string(1) "7" }
// string(2) "21"
代碼中$b跟之前的只是簡(jiǎn)單的賦值,只是在之前多了一部取第一個(gè)元素的引用,但理應(yīng)還是拷貝了一個(gè)新的數(shù)組??墒墙Y(jié)果卻是對(duì)$b的修改,同時(shí)也改變了$a的第一個(gè)元素,而第二個(gè)元素沒(méi)有影響。
從輸出中我們還看到了一個(gè)不尋常的地方,就是數(shù)組第一個(gè)元素的類(lèi)型多一個(gè)‘&'符號(hào)。而這個(gè)正是取引用運(yùn)算符。也就是說(shuō)數(shù)組的第一個(gè)元素已經(jīng)變成了引用類(lèi)型。所以賦值時(shí)也是引用拷貝,而非值拷貝。
這個(gè)問(wèn)題十分奇怪,在開(kāi)發(fā)中也造成了許多不必要的困擾,原本以為拷貝出來(lái)的數(shù)組并沒(méi)有跟原數(shù)組有關(guān)聯(lián),但是就因?yàn)檫@意外出現(xiàn)的引用類(lèi)型,讓我在操作時(shí)也影響到了原數(shù)組。
我也不清楚這算是PHP中的bug,還是有意如此設(shè)計(jì)。在網(wǎng)上找了很久也沒(méi)有對(duì)該方便的相關(guān)解釋?zhuān)挥蠪loat Middle的《PHP: References To Array Elements Are Risky》和 Symmetric Designs的《Problems w/accessing a PHP array by reference》里有談到這個(gè),但是也沒(méi)有講原因。
之后又在PHP的Bug Report中看到幾篇有聯(lián)系的報(bào)告(Bug6417, Bug7412, Bug15025, Bug20993)。有些說(shuō)這是個(gè)Bug,而且已經(jīng)在后邊的版本被修復(fù)。具體我也沒(méi)有明白,只能避免在數(shù)組上使用引用。
更有趣的事情是,如果unset那些引用,只留下一個(gè),那么數(shù)組元素又會(huì)變成不含有引用的正常類(lèi)型。
unset($b);
unset($c);
var_dump($a);
// Output:
//array(2) { [0]=> string(2) "21" [1]=> int(7) }
避免使用PHP的引用
這個(gè)其實(shí)這是PHP Array Manual里面提到的要注意的地方,最常發(fā)生在foreach的之中,希望通過(guò)引用來(lái)改變遠(yuǎn)數(shù)組的值(可參見(jiàn)該篇文章)。
其實(shí)想通過(guò)使用foreach配合引用來(lái)改變數(shù)組元素的值,主要是因?yàn)镻HP的數(shù)組是Associative Array,這種數(shù)組“不定長(zhǎng)度,索引可以不連續(xù),可同時(shí)用字符串和整數(shù)當(dāng)索引”,所以我們無(wú)法用for循環(huán)簡(jiǎn)單增加整數(shù)索引。
當(dāng)然我們可以像下邊的代碼那樣通過(guò)$key直接對(duì)數(shù)組元素改變值,但是這可能存在一定的效率問(wèn)題。
foreach ($array_var as $key => $value)
$array_var [$key] = $newValue;
另一個(gè)常用的引用的地方是在函數(shù)調(diào)用中使用引用傳遞參數(shù)。其主要原因是希望通過(guò)這種方法讓函數(shù)實(shí)現(xiàn)返回多個(gè)返回值。比如我們希望用一個(gè)表示指示函數(shù)是否在執(zhí)行中出現(xiàn)error而導(dǎo)致返回值是無(wú)效的。
但是因?yàn)镻HP的函數(shù)是可以返回不同的類(lèi)型的,所以并不需要傳入引用參數(shù)來(lái)作為表示。即使真的需要多個(gè)返回值,也可以通過(guò)返回“以字符串為主鍵的數(shù)組”作為解決方案,只不過(guò)可能需要在文檔中指出每個(gè)元素都是對(duì)應(yīng)那個(gè)結(jié)果。
有一個(gè)比較好操作方式,應(yīng)該是每當(dāng)引用變量不再需要使用時(shí),就即時(shí)對(duì)該變量使用unset讓它切換與內(nèi)容之間的聯(lián)系。而且即使該變量不是引用類(lèi)型,我們確認(rèn)它不再被使用,對(duì)它調(diào)用unset也不會(huì)有什么問(wèn)題。至少保證在之后對(duì)該變量重新賦值時(shí),并不會(huì)影響到之前的結(jié)果。
相關(guān)文章
php日期轉(zhuǎn)時(shí)間戳,指定日期轉(zhuǎn)換成時(shí)間戳
UNIX時(shí)間戳和格式化日期是我們常打交道的兩個(gè)時(shí)間表示形式,Unix時(shí)間戳存儲(chǔ)、處理方便,但是不直觀(guān),格式化日期直觀(guān),但是處理起來(lái)不如Unix時(shí)間戳那么自如,所以有的時(shí)候需要互相轉(zhuǎn)換,下面給出PHP日期轉(zhuǎn)時(shí)間戳、MySQL日期轉(zhuǎn)換函數(shù)互相轉(zhuǎn)換的幾種轉(zhuǎn)換方式2012-07-07
PHP生成指定范圍內(nèi)的N個(gè)不重復(fù)的隨機(jī)數(shù)
今天小編就為大家分享一篇關(guān)于PHP生成指定范圍內(nèi)的N個(gè)不重復(fù)的隨機(jī)數(shù),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
php從數(shù)組中隨機(jī)選擇若干不重復(fù)元素的方法
這篇文章主要介紹了php從數(shù)組中隨機(jī)選擇若干不重復(fù)元素的方法,涉及php數(shù)組操作的相關(guān)常用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03
詳解WordPress中分類(lèi)函數(shù)wp_list_categories的使用
這篇文章主要介紹了詳解WordPress中分類(lèi)函數(shù)wp_list_categories的使用,文中羅列其主要參數(shù)的功能和寫(xiě)法,需要的朋友可以參考下2016-01-01
PHP用SAX解析XML的實(shí)現(xiàn)代碼與問(wèn)題分析
近日在做一個(gè)解析XML的小程序,因?yàn)榉?wù)器是PHP4的,XML解析函數(shù)只能用SAX方式的xml_parser來(lái)解析了。2011-08-08

