夜貓子 回復于:2004-03-25 15:59:45 |
這是個很好的話題,期待longnetpro發表看法,其實我也需要好好學一下 |
dualface 回復于:2004-03-25 16:42:53 |
簡單的說下面幾種情況要使用引用
1、當你需要在函數中修改傳入參數本身的值: [code:1:e8cd69fe63] function test(& $value) { $value = 1234; } $x = 1; echo "x=$x<BR />"; test($x); echo "x=$x<BR />"; [/code:1:e8cd69fe63] 2、通過函數返回某個值時不希望產生對象的另一個拷貝: [code:1:e8cd69fe63] class myobject { var $_name = null; function myobject($name) { $this->_name = $name; } function toString() { return $this->_name; } } function & test($name) { return new myobject($name); } $obj = & test('object'); echo $obj->toString(); [/code:1:e8cd69fe63] 要注意的是上面的倒數第二行代碼如果改為 $obj = test('object'); 就一樣會產生一個拷貝。所以要想從函數返回引用,除了在申明函數時在函數名前面加上"&"外, 賦值的地方一樣要記得使用"&"。一般來說我只在這兩種情況使用引用。 Zend Development Environment中關于這個問題的說明:使用引用作為參數不但不能夠提高性能, 反倒會降低性能。 至于實際使用時是不是真的降低了性能,沒做過測試就不發表意見了。 |
sports98 回復于:2004-03-25 17:23:50 |
[quote:7766615022]
class myobject { var $_name = null; function myobject($name) { $this->_name = $name; } function toString() { return $this->_name; } } function & test($name) { return new myobject($name); } $obj = & test('object'); echo $obj->toString(); [/quote:7766615022] 我覺得,我們編寫代碼的時候總不能按編譯語言的方式來處理PHP腳本或其他腳本。 [quote:7766615022] class myobject { var $_name = null; function myobject($name) { $this->_name = $name; } function toString() { return $this->_name; } } function test($name) { return new myobject($name); } $obj =test('object'); echo $obj->toString(); [/quote:7766615022] 我很想知道,去掉了&后我們所要獲得的結果是否相同 如果相同的情況下,使用&對于PHP解釋器來說應該是額外的增加了系統開銷,畢竟PHP是運行在WEB服務的安全限制下的(當然了這點代碼肯定還是沒問題的),我們總不能拿編譯語言的思維方式(不錯使用了 & 是防止了對象的復制,單WEB腳本會在請求結束后釋放掉我們所請求的內存空間) & 我覺得應該是,為了在函數內修改非GLOBAL組中的數據而采用的方式 我個人的利用就是 [code:1:7766615022] class DC { function newclass($handle,$classname) { //初始化新的方法返回 ..... } } $P=new DCC; //這里我將獲得一個新的方法$NP; $P->newclass(&$NP,"DCC.Cpp"); //run $NP->run(); [/code:1:7766615022] |
sports98 回復于:2004-03-25 17:31:18 |
上面表達比較混亂,理解是
類似C++內的引用或指針,引用單一的一個對象而不產生復制的另外一種值傳遞方式。 使用場合: 修改數據的時候希望將原始數據同時修改。 |
infom 回復于:2004-03-25 22:34:45 |
需要修改這個數值的時候用 & 不需要修改的時候就不用。
如果不用,會產生 他的一份拷貝。理論上會多占一點點存儲空間。 如果用了。則會修改他的值。如果需要用原值。就完戲了。 |
shukebeita 回復于:2004-03-25 23:48:29 |
簡單地說能用'&' 的時候,你就用'&',不能用的時候就不要用。
能用 一、對象的一致性。 如果你使用類和對象來編寫程序的話,引用就變得非常重要了,它能夠保證對象之間的賦值能夠和其他語言的對象賦值相一致而不是產生一個對象的副本。在php5 中就沒有這么麻煩了,它能夠正確處理對象賦值,但是產生副本好想要用到一個clone函數才可以。 二、性能。 引用只是建立一個變量的別名。(雖然都說它不是指針,但是我想大多數情況下把它理解成類似指針的東西也沒大關系,如果你知道指針是什么的話。)而通常情況下的變量賦值是需要進行copy操作的所以會慢一些。 下面是在別的論壇上和別人討論時候寫的測試代碼,大家試一試就知道了誰快樂。至于dualface說的會降低性能,我還真不太清楚不知道在哪里看到,什么環境下會產生? [size=20:1a9432e703][color=red:1a9432e703]修改,關于性能的觀點是錯誤了。有的時候引用比較慢 :oops: 以前的例子由個低級錯誤,大家看后面的例子。實在抱歉(長在河邊走,難免會濕鞋,貼子批多了也有副作用)[/color:1a9432e703][/size:1a9432e703] 什么時候引用不起作用 1、$a= & 'Bad sentence';//會報錯 2、對于函數參數 function byRef(&$name) { ... } 你可以 $name = 'Shuke'; byRef($name); 但是不可以 byRef('Shuke');//會報錯 3、對于函數引用方式的參數不能有默認值 function byRef(&$name='Shuke')//同樣會報錯 { ...//這個函數的定義是錯誤的。 } 這一點好像在最新的php5中變成了合法的。 4、還有unset也是挺有意思的,猜一猜下面的代碼運行的結果是什么? [code:1:1a9432e703] $a = 1; $b =& $a; unset ($a); echo('b is'.$b.'<br>'); $a = 1; $b =& $a; unset ($b); echo('a is'.$a.'<br>'); [/code:1:1a9432e703] |
longnetpro 回復于:2004-03-26 07:04:24 |
其實要理解引用與值兩個概念還是比較簡單的。
用一個形象一點的比喻,一個PHP中的變量$a與它所代表的值是這樣的關系: 變量名"$a"(注意這里$a只是一個符號而已,不代表任何值),而它的值比如是3的話,那么3就是放在你前面的一條魚,而那個"$a"就是一把叉子,叉在那條魚上,即$a與它的值就構成了一把叉子叉在一條魚上。那么有另一個變量叫$b,它的值也是3,即$b=3,那么就是有另一把叉子叉在了*另*一條魚上,注意強調是*另*一條魚。意思是說,即使這兩個值是一樣的,但它們在內存中是在不同的地方(因為它們是兩條不同的魚)。 這里及以下的比喻中,變量*名*(強調是變量名)用叉子表示,而其值及該值的存在狀態是用魚來代表的,就好比內存就是一大堆魚,而那個值是其中的一條,注意這里魚只表示內容,沒有名字(你也沒聽說內存中的某單元被叫某名字吧)。 那么引用是什么呢?引用就是用兩把不同的叉子叉到*同一條*魚上。同樣以$a=3為例,已經有一個叫$a的叉子叉到了3這條魚上,雖然魚沒有名字,但它怎么也算是個東西吧,總還是具體存在的,為了方便稱呼,不妨稱這條具體的魚叫"魚3"(注意這里"魚3"不是變量名,只是我為了方便敘述人為起的一個名字)。好,前面的廢話說了一堆,那到底引用與非引用有什么不同呢?看一下$a=$b這個式子。這個怎么用叉和魚的關系來解釋?下面注意了: $a = 3 => $a 叉在 "魚3"上 $a = $b => 這中間的過程是這樣的: 這個式子是傳值,于是就先由“魚場的工作人員(這里當然是PHP解釋器了)” *克隆*出一個"魚3"來,估且叫它 “魚3一撇”,這個“魚3一撇”與“魚3”是完全一樣的東西,但卻是兩個實體,就是說它們在那一大堆魚中間是不同的(雖然它們看起來完全相同),說白了就是“魚3一撇”與“魚3”是兩個東西;好,這個克隆完成之后,“魚場的工作人員”就將$b這個叉子叉到了“魚3一撇”身上了,于是$b的值也等于3了。但兩個3在內存中卻不是一個地方。 我想前面說得這么復雜,后面大家應該會有點概念了吧。$a = $b是上面的解釋,那么 $a =& $b是個什么概念呢?很簡單,就是$b這把叉子直接叉到了“魚3”身上,根本沒有克隆一個什么“魚3一撇”出來。就是說倒霉的“魚3”身上被叉了兩把叉子,所以一旦“魚3”被挖了一塊的話,兩把叉子叉的魚都少了一塊,因為它們叉的本來就是同一條魚。上面的話用式子表達的話,就是如果$a --(意思是“魚3”被$a挖了一塊),那好,$b也只能為2了,因為$b叉的那條魚就是被$a挖的那條魚。 先說這么多吧,要消化還得一陣子。開始我也不理解為什么在PHP中的引用不能被理解為指針,后來明白原理后發現真的不能理解為指針,因為PHP賦值語句與取值表達的二重性使指針這個概念在PHP中并不適用。在PHP中一個變量的名字與值是分開的,但該變量本身卻是兩者的合二為一。不知道這句話大家能不能理解。就是說$a這個變量在PHP中既是名字又是值,但在底層,這個變量卻被分開成名字與值兩個部分。在底層,名字就類似于指針,而值只是內存中存這個值的一個區域。而在PHP層面,你將一個值賦給$a,比如$a=3這個式子,這時$a這個變量充當的角色在底層其實是個指針;而當你寫echo $a時,$a這個變量在這時卻充當了在內存中那個值的角色,因為在C中的你可能得這么寫 echo *a,這個星號在這里就是明確指出這個a是一個指針,在JAVA中就更清楚了,除了基本類型變量,其余的全是指針(引用)。但是在PHP中,卻并不是這樣。也正因為如此,PHP中的引用就不象C或是JAVA中那么清晰,也因此造成了大家在理解上的困難,PHP的方便性在這時反而成了影響理解的一個障礙。 上面講的是基本原理,其它的如傳遞引用,返回引用等原理都差不多,只要能理解PHP中變量在內存中是怎么工作的,那么對任何引用問題都是輕而易舉就可以掌握并熟練運用的,比如說我現在對這個概念沒有一點的問題。 至于說用引用的優劣性,那是另一個問題。在PHP中由于字符串也被作為標量了,因此對字符串用引用意義不大,除非你要明確地修改原值。在提高效率上有幫助的主要是引用數組及大的對象。因為沒有文檔說在PHP4中數組的傳遞是用引用方式的,因此估且認為它是傳值的,這樣的話,復制或是克隆一個數組的開銷就比較大了,這時可以用引用。還有對象參數傳值也是這樣,雖然PHP4中聲稱對象傳值的復制是bit to bit的一一復制,但如果這個對象本身的成員變量太多,這個開銷也是很大的。當然PHP5中的一個重大改進,也是開發組聲稱的great performance improvement,即是將所有的對象傳遞全部改成引用方式,就是說以后對變量賦一個對象,就不是復制而是傳遞一個地址了,同時為了解決復制的問題,便在類中多了一個__clone函數來進行bit to bit的復制了。 不管怎么說,要想徹底的搞清楚這個問題,一個關鍵就在于搞清楚,一個變量在內存中到底是怎么工作的,*變量、變量的名字、變量的值*這三者在內存中到底是個什么關系,當中間一個因素變化時,另外的因素是怎么相應變化的,搞清楚了這個,我保證你對所有的這類問題解決起來都會得心應手的。 我一向的原則就是:盡量弄清楚為什么。只有知道為什么才能做到舉一返三,變化無窮盡也;反之,如果會照貓畫虎,只是知其然而不知其所以然,那是無論如何都無法變通應用的,這樣就只會總有無窮的為什么,卻不知道為什么會有這么多的為什么,也永遠不可能真正徹底地解決問題。 注:以上用兩個星號括起來的表示強調。按道理來說應該畫個圖就能使大家一目了然,可惜沒有時間,以上連打字帶組織語言就差不多用了四十分鐘。有不明白的再說吧。當然如果有理解更透徹的朋友也請來給大家分享一下吧。 |
sports98 回復于:2004-03-26 09:26:36 |
[quote:64403f0a94]
我想前面說得這么復雜,后面大家應該會有點概念了吧。$a = $b是上面的解釋,那么 $a =& $b是個什么概念呢?很簡單,就是$b這把叉子直接叉到了“魚3”身上,根本沒有克隆一個什么“魚3一撇”出來。就是說倒霉的“魚3”身上被叉了兩把叉子,所以一旦“魚3”被挖了一塊的話,兩把叉子叉的魚都少了一塊,因為它們叉的本來就是同一條魚。上面的話用式子表達的話,就是如果$a --(意思是“魚3”被$a挖了一塊),那好,$b也只能為2了,因為$b叉的那條魚就是被$a挖的那條魚。 [/quote:64403f0a94] 上面這段我個人的理解有所不同,我的個人看法是 我想前面說得這么復雜,后面大家應該會有點概念了吧。$a = $b是上面的解釋,那么 $a =& $b是個什么概念呢?很簡單,就是$b這把叉子直接叉到了“魚3”身上,根本沒有克隆一個什么“魚3一撇”出來。就是說倒霉的“魚3”身上被叉了[color=red:64403f0a94]兩把叉子[/color:64403f0a94](我個人認為還是一把叉子),所以一旦“魚3”被挖了一塊的話,[color=red:64403f0a94]兩把叉子[/color:64403f0a94]叉的魚都少了一塊,因為它們叉的本來就是同一條魚。上面的話用式子表達的話,就是如果$a --(意思是“魚3”被$a挖了一塊),那好,$b也只能為2了,因為$b叉的那條魚就是被$a挖的那條魚。 [color=blue:64403f0a94] $a=&$b=3; 我的理解就是 叉子B叉到了 魚3,但我在說叉子的時候總是不能上書面的(叉子有很多種),于是我就對叉子起了個別名 $a - 我的魚叉,其實我說[b:64403f0a94]我的魚叉[/b:64403f0a94]和你們所聽到的[b:64403f0a94]魚叉[/b:64403f0a94]是同一個實體---叉在魚3身上的那個叉子,注意:我所強調的是魚三身上只有1把叉子(叉子--別名我的叉子)而不是兩把叉子(叉子,我的叉子)。 因此如果你們要求把魚3的頭去了,那么我可能會對你說:“我的魚叉”上魚頭沒了... 與longnetpro的理解不同之處就是 & 等效理解為別名,而不是在思維內重新建立一個同樣的概念實體。 [/color:64403f0a94] |
longnetpro 回復于:2004-03-26 11:08:20 |
這個,怎么說。
其實我的理解應該是叉子才是指針,叉子和魚在一起時才能被稱為一個變量,而魚就單純只是變量的值。 又看了一下英文原文關于引用的,發現了真正混亂的原因——其實是它官方文檔中說的一些內容用中文理解會弄錯,下面我來詳解一下。 Chapter 14. References Explained References in PHP are a means to aclearcase/" target="_blank" >ccess the same variable content by different names. They are not like C pointers, they are symbol table aliases. Note that in PHP, variable name and variable content are different, so the same content can have different names. 首先要搞清楚中間的幾個概念到底指什么。這里面提到了幾個: reference,variable name, variable content 直譯下來就是 引用,變量名,變量內容。但是,這幾個名字到底在PHP中表示什么,可能就會讓大家糊涂。這里以 PHP中的代碼為例。 先用中文描述:PHP代碼中有一個變量 $a,它的值(內容)為3。對于原英文描述中,[b:59c1db9f32]variable name[/b:59c1db9f32]就是[b:59c1db9f32]$a[/b:59c1db9f32],[b:59c1db9f32]variable content[/b:59c1db9f32] 就是 [b:59c1db9f32]3[/b:59c1db9f32],要注意的是,這里的 [b:59c1db9f32]reference[/b:59c1db9f32]指什么?用PHP代碼就是 [b:59c1db9f32]&$a[/b:59c1db9f32],注意這里在變量名$a前加了一個&,原文說的引用就是指的這個形式。說它不是指針,因為它與C語言中的 &a 很相似,而&a就是一個指針,原文這樣強調&$a不是指針,也是這個意思。那么&$a為什么不是指針呢?官方沒有說清楚,它只用了一個UNIX文件系統作類比,這樣還是不能解釋清楚。上面的叉子和魚有些接近真實情況了,但還是沒有講得很清晰,剛才又看了一下官方文檔,覺得這次應該講得清楚了。 下面解釋為什么 &$a 不是指針。所謂指針其實是指內存某個區域的地址,還是用C語言的 &a 來做類比,這里 a 假設是個整型變量,a本身表示的就是存貯它值的那塊內存區的別名,就象樓上說的那樣,就是說,在C語言中,a是不存在的,它就是一塊內存區的別名,就象我上例說的"魚3",只是為了稱呼方便,你說a或是"魚3",就是完全指的是那塊內存區。但是,在PHP中,情況不同了, &$a 中的 $a 就并不是象C語言中的那個 a 只是代表那塊內存區(或是說只是那塊內存區的別名)。注意,這里的 a 和 "魚3"這個別名是地址別名,就是說這個名字就代表那個地址,而不是這個名字指向那個地址。在PHP中卻相反,$a 并不是那個地址本身,而是指向那個地址,$a 這時也是一個名字,當然也是某個內存區的別名,但它并不是內容所在的那個內存區,即"魚3",而是在符號表中的某個區域中,因此$a是符號表中的別名,正好就如官方所說的 they are symbol table aliases,意即$a代表符號表中的某塊區域;那這樣就清楚了,在底層,$a也還是個變量,也占某個區域,但是在符號表中,這時&$a就可以理解為符號表中那塊區域的地址(因為$a表示那塊區域,&$a就是那塊區域的地址,在這個方面,&$a的確是指針,只不過它指向的并非$a的內容,而是$a這個名字在符號表中的位置——任何一個變量在編譯時都要放到符號表中,因此每個名字在符號表中都有一個地址,&$a就是指這個,而不是$a的內容即3所在的那塊內存的地址)。因此在這里,在符號表中存貯的內容是$a的值3所在的內存地址,也可以理解為 在底層,$a其實是一個指針,指向變量值所在的地址(符合官方所說的UNIX下文件鏈接與文件內容的關系),而&$a其實是指針的指針,是二級指針。 將PHP與C語言對譯一下(以下的C語言變量都是指針),PHP其實都是通過a,b來存取內容,而與中間的a_table和b_table都不能直接由PHP代碼來控制,因為它們是PHP在編譯中產生的符號表中的地址: $a = 3 就可以理解為 *a_table = 3, a = a_table;或是 **a = 3; 那么 $a = $b = 3 就是 *b_table = 3, b = b_table, *a_table = *b_table 或是 *a_table = 3 (這一步就是克?。? a = a_table,這里a_table與b_table是不一樣的,即$a與$b的值所在的內存區是不同的?;蚴?nbsp;**a = **b = 3; 而 $b = 3, $a =& $b 就是 *b_table = 3, b = b_table, a_table = b_table, a = a_table,從這個順序就發現最后a與b間接都鏈到同一個地方去了?;蚴?nbsp;**b = 3, *a = *b(這一句就是說兩個鏈到一起了); 因此,這樣分析下來,樓上說的就錯了,應該有兩把叉子a和b,不是一個別名,別名其實不存在,因為它們都是第二級的指針。 當然這個東西很大程度上也是只可意會,不可言傳,看來很難說得很清楚了,不過我想應該和我說的不會差得太多,而且我每次使用時都與我預期的結果一模一樣,從來沒有出現過不同,我認為PHP解釋器對源代碼編譯時也一定是這個過程的。 其實還是應該畫個圖才行。以后再說吧。 |
dualface 回復于:2004-03-26 13:58:21 |
我說的性能損失是Zend Development Enviroment的代碼分析功能報告的問題:
[img:2acda4c6d6]http://www.dualface.com/tmp/reference_description.png[/img:2acda4c6d6] 不過剛剛又仔細想了一下,我認為ZDE之所以認為引用性能更差,應該是針對簡單類型的 變量,例如整數??戳松厦娲蠹业挠懻?,我簡單畫了一些圖,希望能夠對討論有所幫助。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_01.png[/img:2acda4c6d6] 左邊是內存中實際保存的值,也就是變量內容,而右邊是符號表,也就是變量名。每個 符號表的項目除了變量名外,還要保存該符號對應的內存地址。從這里看好像PHP的引用 和C的指針是一回事,但實際上是有區別的。 因為php是解釋型語言,因此在執行到具體的代碼前,沒法決定一個變量名對應的實際內 存區域,所以我認為php采用了一種查表法。 [b:2acda4c6d6]1、當遇到變量定義的代碼時,php解釋器首先在符號表中生成一個新符號項目,然后再 為該變量分配一塊內存。[/b:2acda4c6d6] [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_02.png[/img:2acda4c6d6] [b:2acda4c6d6]2、當在表達式中使用該變量時,php解釋器從符號表中搜索該變量名,獲得實際的內存 地址后進行各種處理。[/b:2acda4c6d6] [b:2acda4c6d6]3、當調用一個函數時,php解釋器首先在符號表中創建一個新項目,并將符號對應的內 存區域復制一塊出來。這樣在函數中就使用的是新生成那個符號了。[/b:2acda4c6d6] [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_03.png[/img:2acda4c6d6] [b:2acda4c6d6]4、當從函數返回一個值時,php解釋器也是首先在符號表中創建新項目、復制內存區域。所以像下面的代碼實際上進行了[color=red:2acda4c6d6]四次[/color:2acda4c6d6]內存分配和[color=red:2acda4c6d6]三次[/color:2acda4c6d6]內存區域復制操作:[/b:2acda4c6d6] [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_04.png[/img:2acda4c6d6] [code:1:2acda4c6d6] function test($b) { $b = $b + 1; return $b; } $a = 1; $c = test($a); // 代碼片斷 01 [/code:1:2acda4c6d6] 第一次內存分配: $a = 1; 由于$a沒有存在于符號表中,所以php解釋器生成了新的符號項目,并分配了內存區域來 保存$a的值:1 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_05.png[/img:2acda4c6d6] 第二次內存分配和第一區域拷貝: test($a) 用$a做參數調用test函數時,php解釋器首先生成了$b這個符號項目并為其分配內存,接 著將$a對應的內存區域復制到$b對應的內存區域。這樣$b的值就和$a一樣了。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_06.png[/img:2acda4c6d6] 第三次內存分配和第二次區域拷貝: return $b; 函數結束返回值時,php解釋器又生成一個新的符號項目(當然這個名字就是php解釋器 內部的名字了,從程序員的角度來看就是一個匿名符號)和分配對應的內存區域,然后 把$b對應的內存區域復制到這個匿名符號對應的區域。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_07.png[/img:2acda4c6d6] 第四次內存分配和第三次區域拷貝: $c = .... 由于$c是個新符號,所以再進行了一次內存分配操作。接著將test函數返回的匿名符號 對應的內存區域復制到$c對應的內存區域。執行完畢后,臨時性的匿名符號及其對應的 內存區域就不再使用了。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_08.png[/img:2acda4c6d6] 如果上面的程序是在c語言里面,那么只有兩次內存分配操作: [code:1:2acda4c6d6] int test(int b) { b = b + 1; return b; } int a = 1; int c = test(a); [/code:1:2acda4c6d6] 只有在聲明int a和int c時才會分配內存,而將a的值傳遞到test函數時是將a的值復制 到了CPU寄存器中,在test進行運算后的值仍然保存在CPU寄存器中返回。最后復制到c 對應的內存區域。 [b:2acda4c6d6]5、如果函數使用引用作為參數,那么整個過程就有所變化:[/b:2acda4c6d6] [code:1:2acda4c6d6] function test(& $b) { $b = $b + 1; return $b; } $a = 1; $c = test($a); // 代碼片斷 02 [/code:1:2acda4c6d6] 首先,聲明$a引起第一次創建新符號和內存分配。然后test($a)時由于是傳遞引用,所以 php解釋器只會生成一個$b符號,但不會再分配新內存,而是讓$b指向$a對應的內存區域。 接下來的 $b = $b + 1; 操作實際上就改變了$a對應的內存區域的內容。 最后test函數返回值的時候仍然會創建一個匿名符號并為其分配內存,接下來的操作就和 代碼片斷01沒什么區別了。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_09.png[/img:2acda4c6d6] [b:2acda4c6d6]6、如果函數返回引用:[/b:2acda4c6d6] [code:1:2acda4c6d6] function & test(& $b) { $b = $b + 1; return $b; } $a = 1; $c = & test($a); // 代碼片斷 03 [/code:1:2acda4c6d6] 在函數返回前的所有過程都和代碼片斷02相同,不同的地方從函數返回時開始。 首先由于函數是返回引用,所以php解釋器創建的匿名符號指向的是$b對應的內存區域。 而 $c = & test($a); 的結果是$c這個新符號指向匿名符號對應的內存區域。 由于函數又是用引用作為參數,所以代碼片斷03的最終結果就是只有一塊內存被分配出來, 而$a、$b、$c以及那個臨時的匿名符號都指向這塊內存。 [img:2acda4c6d6]http://www.dualface.com/tmp/reference_discuss_figure_10.png[/img:2acda4c6d6] 至于性能問題嘛,我去打幾盤CS再陪女朋友逛街回來再寫 :P |
dualface 回復于:2004-03-26 14:03:14 |
噢噢,圖片還限制了顯示寬度和高度說? |
shukebeita 回復于:2004-03-26 15:03:37 |
我上面關于引用能夠提高性能的說法是錯誤的,感謝dualface。正確的速度測試應該看下面的例子。
[code:1:09372efb5c] <?php function getmicrotime() { list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec); } //initialize big array1 $bigArray1 = array(); for($ii=1; $ii<20; $ii++) { $bigArray1[$ii]=$ii.'value'; } //initialize big array2 $bigArray2 = array(); for($ii=1; $ii<20; $ii++) { $bigArray2[$ii]=$ii.'value'; } //fuction by reference function passByRef(&$param) { //var_dump($param); reset($param); while($kek=key($param)) { $param[$key].='-Processed'; echo($param[$key]) ; next($param); } } //function by copy function passByCopy($param) { //var_dump($param); reset($param); while($kek=key($param)) { $param[$key].='-Processed'; echo $param[$key] ; next($param); } } //Call passByRef 50 times $startTime = getmicrotime(); for($ii=0; $ii<5; $ii++) { passByRef($bigArray1); } $endTime = getmicrotime(); $refTime = $endTime-$startTime; //Call passByCopy 50 times $startTime = getmicrotime(); for($ii=0; $ii<5; $ii++) { passByCopy($bigArray2); } $endTime = getmicrotime(); $copyTime = $endTime-$startTime; echo('<hr>Array Test reference time is '.$refTime.'<br>'); echo('Array Test copy time is'.$copyTime.'<br>'); ?> [/code:1:09372efb5c] |
夜貓子 回復于:2004-03-26 16:00:08 |
對php的引用一直沒有花時間仔細考慮過,我是個懶人,不是迫切的需求我一般不去管。
仔細看了longnetpro對于引用的講解,發覺原來和python里對變量的處理非常相似,也就是“引用計數”的機制,內存里的某塊內容被分配給某個變量,那么這塊內存的引用計數就是1,如果建立了這個變量的一個引用,那么這塊內存的引用計數就為2,以此類推。當引用計數為0的時候,這塊內存就會被釋放掉。以此來理解shukebeita的代碼就是這樣 [code:1:66a2e2d037] /* 粗看感覺$b是$a的引用,$a都被unset了,$b也應該不存在了吧 */ $a = 1; // 分配一塊內存,內容為1,這塊內存的引用計數為1 $b =& $a; // 建立了$a的引用$b,內存的引用計數變成了2 unset ($a); // unset $a以后,內存的引用計數減少成為1 echo('b is '.$b.'<br>'); // 由于引用計數還有1,因此這塊內存并沒有被釋放掉,因此可以打印出"b is 1<br>" /* 下邊這一段不如上邊一段有迷惑性,呵呵 */ $a = 1; $b =& $a; unset ($b); echo('a is '.$a.'<br>'); [/code:1:66a2e2d037] 對引用的理解一直就停留在如果希望函數內部的修改能夠影響到函數外,那么就用引用傳值,希望幾位有心得的兄弟能專門說一說在開發類的時候使用引用的一些技巧。 |
sports98 回復于:2004-03-26 17:19:07 |
看了 夜貓的對代碼的解釋感覺還是贊同的
現在我覺得以上的討論基本是可以理解引用在PHP中到底是個什么概念了 文章已經收藏~ |
dianker 回復于:2004-03-26 18:22:17 |
謝謝大家了,我插不上嘴,只能說聲謝謝....
當然收藏了! |
longnetpro 回復于:2004-03-27 04:24:51 |
看來還是本人表達不佳,說了這么半天還沒有說清楚。不過經過討論,總算差不多了。
非常感謝dualface的圖,他畫的圖就是我想畫的,和我心目中的簡直是一模一樣。我原來寫過一篇英文的關于引用的,說了五六種引用賦值、傳遞及返回的簡單分析,正好對應這位兄弟的幾個圖,真是一模一樣,連匿名符號都一樣的,只是我當時好象用的是result來代替。dualface(雙面人)老兄看來理解得非常地透徹了。我建議大家仔細看他的圖,那是最形象化的說明。我講了那么多,看來還是沒有他的幾張圖來得清楚。 的確PHP解釋時將所有的腳本變量名先存在一個符號表中,然后通過符號表與內存進行映射,同時腳本級的引用對應的就是符號表中映射到同一內容所在的地址——這可能是當時設計ZEND的時候就用的是這個策略,倒不一定是因為PHP是解釋語言,比如說PERL就有可能是真正的指針(說錯了請指正)。 在一般的概念中(比如在C語言中),非指針的變量名就代表其值的那塊內存,引用或是指針就是值或內容的地址。在PHP中,引用就不是這個概念,它并不是值或是內容的地址,而是[b:360bd6edb5]符號表中的變量的名字(不是變量本身)[/b:360bd6edb5]的地址。 還有夜貓子兄說的引用記數,其實就是這么回事,一直忘了說unset的問題,它的確就是引用記數。官方也說得很清楚,unset對引用來說只是unset了名字與內容之間的連接,而并不一定unset其內容本身,就是說讓內部指針置空了。如果內容那塊內存沒有被孤立(還有變量指向它)它就不會被釋放,就是說引用記數沒有減到0,內容區域就不會被釋放。順便說一句貓兄在代碼中有一句說錯了,他說 $b =& $a; // 建立了$a的引用$b,內存的引用計數變成了2 其實并不是建立了$a的引用$b,而是建立了內存的引用$b,因此內存的引用計數變為2,即兩個魚叉叉到了同一條魚上了。之所以這么說,因為官方有一句話說“建立引用并不是指針賦值,而是將兩個變量名同時綁定(bind)到一個內容上”。 關于到性能問題,我個人認為,基于引用的原理,在對簡單類型方面,不用引用比較好(二級指針用多了會降低效率),除非你明確要改變源數據內容。 對于字符串,有個概念要搞清楚,在PHP中,字符串和整數一樣是被當作簡單類型來對待的,因此你$a = $b = 'xxxx'就是將字符串復制兩次,就是說內存中應該有兩個'xxxx',在符號表中,$a、$b就分別指向這兩塊內存區的起始點。而在C語言中,卻是兩個變量指向同一塊內存的起始點,因為在C中字符串是char *的,是個指針。這樣,如果你對一個[b:360bd6edb5]很大[/b:360bd6edb5]的字符串[b:360bd6edb5]只是讀而不是寫[/b:360bd6edb5]的話,不妨用引用賦值,這樣可以減少一次復制過程。如果要對字符串進行修改,如果只是想改變其中某一個字符的值的話,用$a{$i}這種形式是最快的,因為它就是直接去改源字符串了;而如果需要對字符串進行非定長處理,比如截取或是插入等等,這時PHP編譯器則會根據不同的情況來處理了,比如 $s = substr($s, 1),這個式子,因為源和目標都是$s,可能在優化時就不用再復制了,直接把$s的指針往后挪一個(當然要保證引用計數為1的情況下,就是這個字符串只被$s一個變量用),而其它任何情況可能都要復制一遍了,因此對大的字符串來說效率變化比較明顯。這個有點類似于JAVA中的String與StringBuffer的區別,String是只讀的,如果要處理在內存中就中new出一個新的String出來,因此如果循環處理次數很多的情況下,用String極慢;StringBuffer卻與之相反,可以直接改源字符串與C中的char*相似,所以對字符串做大量操作的情況下一般用StringBuffer;在PHP中,字符串同時兼有以上兩種特性,在一般情況下是String這種形式的,在引用時,根據當時上下文或是引用記數PHP解釋器動態地處理,在用$s{$i}這種形式時,就是StringBuffer這種形式,因此對字符串處理是時候,你也要看上下文及字符串的大小相應地處理。 對于數組,如果比較大,建議用引用,但要注意,用引用就有可能改變原值,如果對原數組只讀的話,不妨大膽用引用,沒有關系。在PHP中,建立一個數組花的時間最多,因為要散列數組的key和value并存貯(分配內存),特別是數組很大的情況下;又由于PHP中的數組全部都是關聯數組,也叫散列數組,就是說在PHP中數組沒有下標的概念,而是通過鍵值去散列查找值的,不是直接的地址對應,因此比較慢。在沒有特殊要求或是必需的情況下,最好少用數組。對只讀數組,在引用時不妨大膽用引用,在賦值時則要考慮上下文是否用引用賦值,因為一旦這樣賦了值,對引用計數會有影響,引用多了,搞不好哪里被別的變量改了,調試起來又云里霧里的。 對象的用法相對比數組簡單一點,這個可以直接理解為C++或是JAVA的對象都可以,大多數情況下對象應該用引用,這也是為什么到了PHP5對對象的賦值改成引用賦值了;而相反對對象的復制,也叫克隆反而沒有那么常見,因此PHP5中,基類中有一個特殊的__clone()方法,其實就起了PHP4中直接賦值這個功能。因此在PHP4中,如果某個對象的成員變量(注意只是成員變量,包括沒有用var顯示定義但是在函數中用$this->這樣的方式隱式定義的,也就是用var_dump能顯示出來的那些成員變量)太多的話,也建議用引用,除非你人為想克隆一個對象出來(這種情況其實相當少見),在PHP5中,我認為有可能會在對象問題上與PHP4的程序發生沖突,因此對PHP4寫的程序來說,我認為最好一直都用引用。 |
toplee 回復于:2004-03-28 01:01:50 |
呵呵,有收獲,高手過招 |
HonestQiao 回復于:2004-03-28 09:25:25 |
強烈建議使用php的人,好好看看C++,我正在看,我能估計,對我在PHP編程方面的水平有大幅提高 |
冰川火云 回復于:2004-03-28 21:41:49 |
好貼,學習 |
flytod 回復于:2004-04-12 00:12:28 |
dualface ,你畫的圖看不見了。能再顯示一下嗎?謝謝。 |
dualface 回復于:2004-04-12 01:09:52 |
不小心刪除了,現在恢復了。 |
yangshiqi 回復于:2004-08-01 23:39:05 |
傳值和傳引用主要是針對變量類型來說的,在php5以前的版本,對于傳值來說,非數組(hash)和對象在函數內部進行處理時,會在內存中初始化一塊新的空間,并將參數復制存放在堆上,而對于整數和字符串,后臺是將該參數的內存地址直接復制使用,也就是說的使用引用.到php5以后,全部使用引用的傳遞.對于復制一個對象來說,$new_obj = clone $old_obj;來復制,而$new_obj = $old_obj;只是簡單的復制內存地址. |
原文轉自:http://www.anti-gravitydesign.com