談談拷貝這件小事

jccjd發表於2019-02-26

當在使用某個物件,而需要對該物件進行大量操作,或者在新的上下文環境中複用該物件的部分或全部資料時,需要對其進行拷貝操作。

拷貝的深淺

  • 我們日常所使用的拷貝,如一個資料夾,或者視訊,音樂的拷貝,使用時可以發現這種拷貝是真正的生成了一個新的檔案,佔用了部分記憶體的。那麼這種就是深拷貝,也就是在計算機中重新開闢了一塊新的記憶體地址用於存放物件。
    而什麼是淺拷貝,淺拷貝只是拷貝了基本的資料型別,而引用資料型別,複製後也是會發生引用,淺拷貝只是指向被複制的記憶體地址。如果引用資料型別的物件發生了改變,那麼淺拷貝出來的物件也會改變

由淺入深

那麼結合程式碼來看看從淺拷貝到深拷貝發生了什麼

<?php

class People {

    protected $_name = `張三`;
    protected $_sex = `男`;
    protected $_age = `18`;

    /**
     * return Name
     */
    public function getName() {
        return $this-> _name;
    }
    /**
     * set name
     */
    public function setName($name) {
        $this->_name = (string)$name;
        return $this;
    }
}
$p1 = new People();
$p2 = $p1;
複製程式碼

上面建立了一個people類,然後例項化了p1,用賦值的方式 建立了p2 這樣就得到了兩個名字,年齡,性別都一樣的人。那麼當我們修改p1或p2時都會改變name

echo $p1->getName();
echo $p2->getName();
//p2改名
$p2->setName(`李四`);
echo $p1->getName();
echo $p2->getName();
//p1改名
$p1->setName(`王五`);
echo $p1->getName();
echo $p2->getName();
複製程式碼

輸出為:張三張三李四李四王五王五

這裡物件的賦值和傳值都是以引用的方式。名字雖然不同但指的是同一個人。所以這種拷貝相當於一個人有個名字和外號,人還是這個人,這種拷貝不是我所想要的。那麼換種方式。用clone來複制物件。

clone函式

$p1 = new People();
$p2 = clone $p1;
echo $p1->getName();
echo $p2->getName();
//p2改名
$p2->setName(`李四`);
echo $p1->getName();
echo $p2->getName();
//p1改名
$p1->setName(`王五`);
echo $p1->getName();
echo $p2->getName();
複製程式碼

那麼這段程式碼用clone關鍵字複製p1物件,現在這個p1物件得到了這個真正的拷貝p2,p1和p2改名時分別都能改成功,而p1和p2都屬於不同的物件,都是獨立的個體了,如果p1有其他的關係,那麼如何呢,就如同你克隆了一個人,儘管這個克隆人和本體的基本屬性相同,但會有相同的記憶嗎會有複雜的社會關係嗎?
顯然並不會

那麼假如這個張三這個人有同學這個類,現在如何讓克隆體也有這個同學類呢。下面精簡一下程式碼

class People {

    public $name = `張三`;
    public $mate;
    /**
     * 建構函式中載入同學物件
     */
    public function __construct()
    {
        $this->mate = new Classmate();
    }
}
/**
 * 同學類
 */
class Classmate {
    public $name ="王五";
}
$p1 = new People();
$p2 = clone $p1;
$p2 ->name = "ll";
echo $p1->name;
echo $p2->name;
$p2->mate->name = "ll";
echo $p1->mate->name;
複製程式碼

然後可以發現p2可以改名字也就是p2的普通屬性實現了深拷貝 而mate物件屬性中的名字也會改變,存在一定的問題

一般有兩種解決方法:

1.重寫clone函式
class People {

    public $name = `張三`;
    public $mate;
    /**
     * 建構函式中載入同學物件
     */
    public function __construct()
    {
        $this->mate = new Classmate();
    }
    
    //重寫clone函式
    public function __clone() {
        $this->mate = clone $this->mate;
    }
}
/**
 * 同學類
 */
class Classmate {
    public $name ="王五";
}
$p1 = new People();
$p2 = clone $p1;
$p2 ->name = "ll";
echo $p1->name;
echo $p2->name;
$p2->mate->name = "ll";
echo $p1->mate->name;//輸出還是王五
複製程式碼

這樣就可以解決了,但是如果classmate中有很多屬性,或者people類要引入很多類,那麼這樣的重寫clone函式就會變得很麻煩

2序列化和反序列化

這種方法不用修改函式比較簡單

<?php

class People {

    public $name = `張三`;
    public $mate;
    /**
     * 建構函式中載入同學物件
     */
    public function __construct()
    {
        $this->mate = new Classmate();
    }


}
/**
 * 同學類
 */
class Classmate {
    public $name ="王五";
}
$p1 = new People();
$p2 = serialize($p1);
$p2 = unserialize($p2);
$p2 ->name = "ll";

$p2->mate->name = "ll";

echo $p1->mate->name;

複製程式碼

還可以用json_encode之後再json_decode,實現賦值和法二一樣 

相關文章