話說我這個使用參照到病態的人,來寫這個應該蠻適合的。

首先要提得是為甚麼要使用參照?
最重要的理由是為了減少複製變數的成本,這個成本包括記憶體的使用以及時間上的消耗。
如果你看過PHP的原始碼,PHP的變數基本上是由ZVAL這個結構所控制的,所以不管是不是使用參照,當我們作函數的參數傳遞、返回值以及變數賦值的時候,這個結構的產生是不可避免的。

這個問題還好,畢竟這個結構很小,但重點是一些複雜的變數型態,像是陣列、字串、物件等等,這些在複製花的成本上都比數字、邏輯值來的大,當然這也不能一概而論,但重點是在於,陣列、字串、物件這些型態的實際佔用的記憶體空間並不固定,更遑論像物件的複製可能還會有其他的問題產生。
當然啦,數字與邏輯值這些型態,用參照也得不到什麼額外的好處就是。

我個人是比較欣賞JavaScript的作法,傳值還是傳參照由型態決定,而決定的方式有公開的標準。

接下來我來談什麼樣的情況非得用參照不可?我發現有兩個原則:
  1. 無法確定你要操作的變數「在哪裡」的時候。
  2. 你要修改的是「原件」而非「副本」的時候。
第一種情況不太常見,不過程式寫多就會碰到,尤其是當你像我使用參照到病態的地步時,就會知道我說得是啥。
第二種情況則是比較常見,尤其函數的參數傳遞、返回值的應用。

另外要提的是,當一個變數被宣告為參照時,後續的賦值動作「全部」會被當成對被參照的變數作賦值動作,沒有辦法解除這種狀態,除非你unset這個參照變數。

除了上述的原則,有幾個情況建議使用參照:
  1. 函數的參數、返回值若為陣列、物件、資源時。
  2. 字串則要看函數調用時是否會直接給值而定,若不常發生直接給值的情況,也建議用參照。
對於函數傳回值的部份,有一點要注意。
當我們的自訂函數前面加上&符號時,代表會傳回參照,這個部份一直到調用函數然後傳回值這一段都是正確的。
但是!
當我們要把這個傳回值,直接變成另一個函數的參數,或是賦值給另外一個變數時,就不一定是參照了。

另外要說明的是,參照有其可用範圍,其範圍存在於函數之內,但不存在於此函數中呼叫另一函數之中,唯一的例外是此參照的對象是物件。

舉例說明:
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
創作者介紹

失落的技術

HACGIS 發表在 痞客邦 PIXNET 留言(12) 人氣()


留言列表 (12)

發表留言
  • 遊手好閒的石頭成
  • はよ。
    PHP 的參照功能,我也覺得有些不便。
    1. 不能宣告唯讀參照, example: function abc(const &$a)
    2. 當值為常數時,不會自動建立參照。例如直接傳字串的情形; example: abc('hello');
    在 C/C++ ,這些問題都不存在...
  • HACGIS
  • 1. 唯讀參照要能存在,PHP的記憶體回收機制要作修改。
    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++中這屬於編譯時期的型態檢查動作,我想不通會跟記憶體回收機制扯上關係。
  • HACGIS
  • 唯讀參照的參照,也不能修改才對~
    如果你細想的話,就會知道為何會跟記憶體回收機制有關。
    至於常數參照,有些會有警告,有些則不會,端看你有沒有打開那個選項,這些東西只是讓設計者方便,其實若是程式設計師不注意,還是會犯錯誤的。
  • 遊手好閒的石頭成
  • 呃,一時口誤,我說的是指向唯讀內容的參照,而不是「唯讀的參照」。兩者在 C/C++ 中意義不同。

    //指向唯讀內容的參照
    void f1(const string& x){}

    //唯讀參照 (這才是真貨)
    void f2(string const& x) {}

    但在 PHP 中,似乎用不到真的唯讀參照。
  • HACGIS
  • 恩,如果哪一天,PHP有實做readonly關鍵字的話,那就有你要的功能囉~
  • 遊手好閒的石頭成
  • 又錯,我把指標和參照弄混了...
    在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 並不影嚮記憶體回收機制吧?
  • HACGIS
  • 這麼說好了,PHP的記憶體管理的方式與JavaScript都是屬於參考計數型一類的管理方式。
    所以當變數被初值化時,不一定會真的去配置記憶體,有時候要到這個變數被第一次修改時才會進行內容的記憶體配置。
    這麼說你應該了解,若要能夠有唯讀參照,記憶體管理必須另外做一些工作,來維持參照的不變性。
  • 遊手好閒的石頭成
  • read-only reference 的意思 (引用C/C++的意思) 是可用 getter ,不可用 setter 的參照。依此意我設計了一個 PHP 版本的 read-only reference....
    示範用,只能參照標準個體(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);

    ?>
  • HACGIS
  • 我是覺得不要在語法上打轉,要考慮PHP在執行階段對於該參照所指涉的變數的readonly特性去探討才不會失焦。
    我之前有說過,PHP的參照在未提取前可以指到「任何」東西上面,包括並未存在的變數也行,這是動態語言的特性。
    若真的有「唯讀參照」這東西,必須使用某個關鍵字,例如:const或是readonly來修飾,在parse階段可以發現所參照的對象是否存在。
    另外一點,對於所指涉的變數內容,必須在某個範圍內保證期不變性,這對於記憶體管理或是parse時的檢查都是負擔,所以未來是否會出現唯讀參照我個人是持保留態度。
  • 遊手好閒的石頭成
  • "該參照所指涉的變數的readonly特性",你是說你希望唯讀參照的參照對象也是唯讀?這功能超出我預期。

    我預期的只是用 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 。
  • HACGIS
  • 應該說是有「範圍」的唯讀。
    以你上面舉的例子來看,唯讀參照是在進入某個函數內會造成所指涉的對象「暫時」變成唯讀的狀態。
    那麼我若用另一參照指涉過去,此參照是否亦為唯讀參照?繼承此唯讀參照的物件又如何?會有很多的問題產生。
    這些都會造成額外的開銷,不只是最近的動態語言,像有些C/C++的編譯器,有時需要2pass以上去在編譯時確定指標或參照的特性,來決定語法是否正確。
    所以有些語法,在我們從源碼的角度去審視好像很簡單,但是實做起來就要對各種定義和邊際效益去較真,有時對速度而言是得不償失。

    PING:
    TITLE: PHP 的參照及唯讀參照之實作
    BLOG NAME: 石頭閒語
    PHP5 可以寬鬆態度實踐唯讀參照。如果此一態度下的唯讀參照不是以類別型式而能以語言特性型式實踐,我想效能會更好。但若要以嚴謹態度,令唯讀參照防範程序員的解唯讀動作,則情況就如

找更多相關文章與討論