話說我這個使用參照到病態的人,來寫這個應該蠻適合的。
首先要提得是為甚麼要使用參照?
最重要的理由是為了減少複製變數的成本,這個成本包括記憶體的使用以及時間上的消耗。
如果你看過PHP的原始碼,PHP的變數基本上是由ZVAL這個結構所控制的,所以不管是不是使用參照,當我們作函數的參數傳遞、返回值以及變數賦值的時候,這個結構的產生是不可避免的。
這個問題還好,畢竟這個結構很小,但重點是一些複雜的變數型態,像是陣列、字串、物件等等,這些在複製花的成本上都比數字、邏輯值來的大,當然這也不能一概而論,但重點是在於,陣列、字串、物件這些型態的實際佔用的記憶體空間並不固定,更遑論像物件的複製可能還會有其他的問題產生。
當然啦,數字與邏輯值這些型態,用參照也得不到什麼額外的好處就是。
我個人是比較欣賞JavaScript的作法,傳值還是傳參照由型態決定,而決定的方式有公開的標準。
接下來我來談什麼樣的情況非得用參照不可?我發現有兩個原則:
第二種情況則是比較常見,尤其函數的參數傳遞、返回值的應用。
另外要提的是,當一個變數被宣告為參照時,後續的賦值動作「全部」會被當成對被參照的變數作賦值動作,沒有辦法解除這種狀態,除非你unset這個參照變數。
除了上述的原則,有幾個情況建議使用參照:
當我們的自訂函數前面加上&符號時,代表會傳回參照,這個部份一直到調用函數然後傳回值這一段都是正確的。
但是!
當我們要把這個傳回值,直接變成另一個函數的參數,或是賦值給另外一個變數時,就不一定是參照了。
另外要說明的是,參照有其可用範圍,其範圍存在於函數之內,但不存在於此函數中呼叫另一函數之中,唯一的例外是此參照的對象是物件。
舉例說明:
function &a(){
...
}
function &b(&$a){
$c = &$a; //$c = null
$c = $a; // $c = $a的複製品 (若$a為物件的參照,則$c等同於$a所參照的原件)
$a['a'] = 1; // 修改的是$a的原件(因為$a是用參照的方式傳進b()中)
...
}
$c = b(a());//這裡傳進去的a()的傳回值不是參照,$c取得的也不是b()傳回值的參照
$c = b(&a());//這個會引發錯誤:call_time_pass_reference
//正確作法
$temp = &$a();
$c = &b($temp);
$aa = array();
$bb = &$aa['a']['b']; // 這是OK的,尚未「運算」之前,參照可以指向任何東西
$bb = 1; // 這表示$aa['a']['b'] = 1
首先要提得是為甚麼要使用參照?
最重要的理由是為了減少複製變數的成本,這個成本包括記憶體的使用以及時間上的消耗。
如果你看過PHP的原始碼,PHP的變數基本上是由ZVAL這個結構所控制的,所以不管是不是使用參照,當我們作函數的參數傳遞、返回值以及變數賦值的時候,這個結構的產生是不可避免的。
這個問題還好,畢竟這個結構很小,但重點是一些複雜的變數型態,像是陣列、字串、物件等等,這些在複製花的成本上都比數字、邏輯值來的大,當然這也不能一概而論,但重點是在於,陣列、字串、物件這些型態的實際佔用的記憶體空間並不固定,更遑論像物件的複製可能還會有其他的問題產生。
當然啦,數字與邏輯值這些型態,用參照也得不到什麼額外的好處就是。
我個人是比較欣賞JavaScript的作法,傳值還是傳參照由型態決定,而決定的方式有公開的標準。
接下來我來談什麼樣的情況非得用參照不可?我發現有兩個原則:
- 無法確定你要操作的變數「在哪裡」的時候。
- 你要修改的是「原件」而非「副本」的時候。
第二種情況則是比較常見,尤其函數的參數傳遞、返回值的應用。
另外要提的是,當一個變數被宣告為參照時,後續的賦值動作「全部」會被當成對被參照的變數作賦值動作,沒有辦法解除這種狀態,除非你unset這個參照變數。
除了上述的原則,有幾個情況建議使用參照:
- 函數的參數、返回值若為陣列、物件、資源時。
- 字串則要看函數調用時是否會直接給值而定,若不常發生直接給值的情況,也建議用參照。
當我們的自訂函數前面加上&符號時,代表會傳回參照,這個部份一直到調用函數然後傳回值這一段都是正確的。
但是!
當我們要把這個傳回值,直接變成另一個函數的參數,或是賦值給另外一個變數時,就不一定是參照了。
另外要說明的是,參照有其可用範圍,其範圍存在於函數之內,但不存在於此函數中呼叫另一函數之中,唯一的例外是此參照的對象是物件。
舉例說明:
function &a(){
...
}
function &b(&$a){
$c = &$a; //$c = null
$c = $a; // $c = $a的複製品 (若$a為物件的參照,則$c等同於$a所參照的原件)
$a['a'] = 1; // 修改的是$a的原件(因為$a是用參照的方式傳進b()中)
...
}
$c = b(a());//這裡傳進去的a()的傳回值不是參照,$c取得的也不是b()傳回值的參照
$c = b(&a());//這個會引發錯誤:call_time_pass_reference
//正確作法
$temp = &$a();
$c = &b($temp);
$aa = array();
$bb = &$aa['a']['b']; // 這是OK的,尚未「運算」之前,參照可以指向任何東西
$bb = 1; // 這表示$aa['a']['b'] = 1












Recommend to Front page



PHP 的參照功能,我也覺得有些不便。
1. 不能宣告唯讀參照, example: function abc(const &$a)
2. 當值為常數時,不會自動建立參照。例如直接傳字串的情形; example: abc('hello');
在 C/C++ ,這些問題都不存在...
2. 常數參照在C/C++屬於Warning,不過這項Warning在BCB的預設中是不打開的,其他的編譯器我就不清楚了。
常數參照的情形,在 C/C++ 中要視宣告而定。例如:
#include <string>
using namespace std;
void f1(const string& x) {}
void f2(string& x) {}
int main()
{
f1("hello");
f2("hello"); //error
return 1;
}
很合理的使用方式。當一個函數要求傳一個參照過來,又想讓調用者安心、或是讓 compiler 幫設計者檢查自己是不是不小心在函數中動了參照時,就加個 const 表明我這函數絕對不會改參數內容。在C/C++中這屬於編譯時期的型態檢查動作,我想不通會跟記憶體回收機制扯上關係。
如果你細想的話,就會知道為何會跟記憶體回收機制有關。
至於常數參照,有些會有警告,有些則不會,端看你有沒有打開那個選項,這些東西只是讓設計者方便,其實若是程式設計師不注意,還是會犯錯誤的。
//指向唯讀內容的參照
void f1(const string& x){}
//唯讀參照 (這才是真貨)
void f2(string const& x) {}
但在 PHP 中,似乎用不到真的唯讀參照。
在C/C++中, const type& 和 type const& 是一樣的。我前文寫錯了。在 C/C++ 中,參照本身是唯讀的,是一個隱蔽常數,不需要使用 const 修飾。
那麼 const 加上參照是什麼意思呢?這是修飾參照之對象為常值、唯讀之意,令使用者不可透過參照變更內容。所以只要 PHP 實作 const 關鍵字於參照上就有我要的功能了。
關於記憶體回收機制,我想要看使用策略而定。如果參照對象是內容而非符號,那麼當符號重新被指派內容時,參照仍然指向舊內容,使舊內容無法被釋放。若參照對象是符號,則無此問題。
1.參照對象是內容:
$x = array(1,2,3);
$r = &$x;
// $r 指向內容 [1,2,3]
$x = array(7,8,9);
//$x 重新指派內容
//$x 內容是 [7,8,9], $r 指向內容 [1,2,3]
2.參照對象是符號:
$x = array(1,2,3);
$r = &$x;
//$r 指向符號 $x.
$x = array(7,8,9);
//$r 指向符號 $x ,才連結到內容
但若參照作為函數回傳值時,則需要一個暫時參照,暫時參照的對象是內容而非符號。暫時參照作用於調用者接收函數回傳值之時,完成後就被釋放。
我測了一下, PHP 似乎就是這樣的使用策略。若依上述策略,則參照是否 readonly 並不影嚮記憶體回收機制吧?
所以當變數被初值化時,不一定會真的去配置記憶體,有時候要到這個變數被第一次修改時才會進行內容的記憶體配置。
這麼說你應該了解,若要能夠有唯讀參照,記憶體管理必須另外做一些工作,來維持參照的不變性。
示範用,只能參照標準個體(standard object)。
<?php
class Reference {
protected $_target;
public function __construct(&$target) {
$this->_target = &$target;
}
public function __set($property, $v) {
$this->_target->$property = $v;
}
public function __get($property) {
return $this->_target->$property;
}
}
class ConstReference extends Reference {
public function __set($k, $v) {
throw new Exception('read-only reference!');
}
}
$x->value = 0;
echo '$x is ';
print_r($x);
$ref = new Reference($x);
$constRef = new ConstReference($x);
echo "Value is {$ref->value} \n";
$ref->value = 100;
echo '$x is ';
print_r($x);
echo "Value is {$constRef->value} \n";
$constRef->value = 200;
echo '$x is : ';
print_r($x);
?>
我之前有說過,PHP的參照在未提取前可以指到「任何」東西上面,包括並未存在的變數也行,這是動態語言的特性。
若真的有「唯讀參照」這東西,必須使用某個關鍵字,例如:const或是readonly來修飾,在parse階段可以發現所參照的對象是否存在。
另外一點,對於所指涉的變數內容,必須在某個範圍內保證期不變性,這對於記憶體管理或是parse時的檢查都是負擔,所以未來是否會出現唯讀參照我個人是持保留態度。
我預期的只是用 const 關鍵字 disable 參照的 setter 。因此 user 可以直接地變更變數內容,但不能透過參照去變更內容。
承前文之 php碼,加上:
$x->value = 300;
//直接調用變數 setter,Ok.
$constRef->value = 300;
//調用唯讀參照 setter,丟出例外.
要達到你說 "PHP的參照在未提取前可以指到「任何」東西上面,包括並未存在的變數也行" 之需求,則可將前文php碼的建構子改為可以不立即指派參照對象,並將 setter 加上若尚未指派參照對象則傳回 false (或丟出例外的) 的 code 。
因為 PHP5 只能 overload setter 和 getter ,所以現階段用 PHP 可以「有條件地」實作我要的唯讀參照,條件是參照對象為 array 或 object 。
以你上面舉的例子來看,唯讀參照是在進入某個函數內會造成所指涉的對象「暫時」變成唯讀的狀態。
那麼我若用另一參照指涉過去,此參照是否亦為唯讀參照?繼承此唯讀參照的物件又如何?會有很多的問題產生。
這些都會造成額外的開銷,不只是最近的動態語言,像有些C/C++的編譯器,有時需要2pass以上去在編譯時確定指標或參照的特性,來決定語法是否正確。
所以有些語法,在我們從源碼的角度去審視好像很簡單,但是實做起來就要對各種定義和邊際效益去較真,有時對速度而言是得不償失。
Comment Permissions: Allow commenting