深入讨论PHP5对象复制技术
此文将由浅入深的讨论PHP5的对象复制技术 原创文章 请尊重版权 有错误或则不当之处还希望能够指出来
对象复制的由来
为什么对象会有“复制”这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码
PHP代码
- /**
- * 电视机类
- */
- class Television
- {
- /**
- * 屏幕高度
- */
- protected $_screenLength = 300;
- /**
- * 屏幕宽度
- */
- protected $_screenHight = 200;
- /**
- * 电视机外观颜色
- */
- protected $_color = 'black';
- /**
- * 返回电视外观颜色
- */
- public function getColor()
- {
- return $this->_color;
- }
- /**
- * 设置电视机外观颜色
- */
- public function setColor($color)
- {
- $this->_color = (string)$color;
- return $this;
- }
- }
- $tv1 = new Television();
- $tv2 = $tv1;
这段代码定义了一个电视机的类 Television , $tv1为一个电视机的实例,然后我们按照普通的变量赋值方式将$tv1的值赋给$t2。那么现在我们拥有两台电视机$tv1和$tv2了,真的是这样的吗?我们来测试一下。
PHP代码
- echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
-
echo '
'; - echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
-
echo '
'; - //把tv2涂成白色
- $tv2->setColor('white');
- echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
-
echo '
'; - echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是white
首先我们看到tv1和tv2的颜色都是black,现在我们希望tv2换个颜色,所以我们将它的颜色设置成了white,我们再看看tv2的颜色,确实成为了white,似乎满足了我们的要求,可是并没有想象中的那么顺利,当我们接着看tv1的颜色的时候,我们发现tv1也由black边成了white。我们并没有重新设置tv1的颜色,为什么tv1会重black变成white呢?这是因为PHP5中对象的赋值和传值都是以“引用”的方式。PHP5使用了Zend引擎II,对象被储存于独立的结构Object Store中,而不像其它一般变量那样储存于Zval中(在PHP4中对象和一般变量一样存储于Zval)。在Zval中仅存储对象的指针而不是内容(value)。当我们复制一个对象或者将一个对象当作参数传递给一个函数时,我们不需要复制数据。仅仅保持相同的对象指针并由另一个zval通知现在这个特定的对象指向的Object Store。由于对象本身位于Object Store,我们对它所作的任何改变将影响到所有持有该对象指针的zval结构----表现在程序中就是目标对象的任何改变都会影响到源对象。.这使PHP对象看起来就像总是通过引用(reference)来传递。所以以上的tv2和tv1其实是指向同一个电视机实例,我们对tv1或则tv2所做的操作其实都是针对这同一个实例。因此我们的“复制”失败了。看来直接变量赋值的方式并不能拷贝对象,为此PHP5提供了一个专门用于复制对象的操作,也就是 clone 。这就是对象复制的由来。
用clone(克隆)来复制对象
我们现在使用PHP5的clone语言结构来复制对象,代码如下:
PHP代码
- $tv1 = new Television();
- $tv2 = clone $tv1;
- echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
-
echo '
'; - echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
-
echo '
'; - //把tv2换成涂成白色
- $tv2->setColor('white');
- echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
-
echo '
'; - echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
这段代码的第2行,我们用clone关键字复制tv1,现在我们就拥有了一份真正的tv1的拷贝tv2,我们还是按照之前的方法来检测复制是否成功。我们可以看到,我们将tv2的颜色换成了white,tv1的颜色还是black,这样我们的复制操作就成功了。
__clone魔术方法
现在我们考虑到这样一个情况,每一台电视机应该都有自己的编号,这个编号如同我们的身份证号码一样应该是唯一的,所以当我们在复制一台电视机的时候,我们不希望这个编号也被复制过来,以免造成一些麻烦。我们想到的一个策略是将赋值出来的电视机的编号清空,然后再按照需求来重新分配编号。
那么__clone魔术方法就是专门用来解决这样的问题,__clone魔术方法会在对象被复制( 也就是clone操作)的时候被触发。我们修改了电视机类Television的代码,添加了编号属性和__clone方法,代码如下。
PHP代码
- /**
- * 电视机类
- */
- class Television
- {
- /**
- * 电视机编号
- */
- protected $_identity = 0;
- /**
- * 屏幕高度
- */
- protected $_screenLength = 300;
- /**
- * 屏幕宽度
- */
- protected $_screenHight = 200;
- /**
- * 电视机外观颜色
- */
- protected $_color = 'black';
- /**
- * 返回电视外观颜色
- */
- public function getColor()
- {
- return $this->_color;
- }
- /**
- * 设置电视机外观颜色
- */
- public function setColor($color)
- {
- $this->_color = (string)$color;
- return $this;
- }
- /**
- * 返回电视机编号
- */
- public function getIdentity()
- {
- return $this->_identity;
- }
- /**
- * 设置电视机编号
- */
- public function setIdentity($id)
- {
- $this->_identity = (int)$id;
- return $this;
- }
- public function __clone()
- {
- $this->setIdentity(0);
- }
- }
下面我们来复制这样的一个电视机对象。
PHP代码
- $tv1 = new Television();
- $tv1->setIdentity('111111');
- echo 'id of tv1 is ' . $tv1->getIdentity();//111111
-
echo '
'; - $tv2 = clone $tv1;
- echo 'id of tv2 is ' . $tv2->getIdentity();//0
我们生产了一台电视机tv1 , 并且设置它的编号为111111,然后我们用clone将tv1复制得到了tv2,这个时候__clone魔术方法被触发,此方法将直接作用与复制得到的对象tv2,我们在__clone方法中调用了setIdentity成员方法将tv2的_identity属性清空,以便我们后面对它进行重新编号。由此我们可以看到__clone魔术方法能让我们非常方便的在clone对象的时候做一些附加的操作。
clone操作的致命缺陷
clone真的能够达到理想的复制效果吗?在某些情况下,你应该会发现,clone操作并没有我们想象中的那么完美。我们将以上的电视机类修改一下,然后做测试。
每台电视机都会附带一个遥控器,所以我们将会有一个遥控器类,遥控器和电视机是一种“聚合”关系(相对与“组合”关系,是一种较弱的依赖关系,因为一般情况电视机就算没有遥控也能正常使用),现在我们的电视机对象应该都持有一个到遥控器对象的引用。下面看看代码
PHP代码
- /**
- * 电视机类
- */
- class Television
- {
- /**
- * 电视机编号
- */
- protected $_identity = 0;
- /**
- * 屏幕高度
- */
- protected $_screenLength = 300;
- /**
- * 屏幕宽度
- */
- protected $_screenHight = 200;
- /**
- * 电视机外观颜色
- */
- protected $_color = 'black';
- /**
- * 遥控器对象
- */
- protected $_control = null;
- /**
- * 构造函数中加载遥控器对象
- */
- public function __construct()
- {
- $this->setControl(new Telecontrol());
- }
- /**
- * 设置遥控器对象
- */
- public function setControl(Telecontrol $control)
- {
- $this->_control = $control;
- return $this;
- }
- /**
- * 返回遥控器对象
- */
- public function getControl()
- {
- return $this->_control;
- }
- /**
- * 返回电视外观颜色
- */
- public function getColor()
- {
- return $this->_color;
- }
- /**
- * 设置电视机外观颜色
- */
- public function setColor($color)
- {
- $this->_color = (string)$color;
- return $this;
- }
- /**
- * 返回电视机编号
- */
- public function getIdentity()
- {
- return $this->_identity;
- }
- /**
- * 设置电视机编号
- */
- public function setIdentity($id)
- {
- $this->_identity = (int)$id;
- return $this;
- }
- public function __clone()
- {
- $this->setIdentity(0);
- }
- }
- /**
- * 遥控器类
- */
- class Telecontrol
- {
- }
下面复制这样的一个电视机对象并且观察电视机的遥控器对象。
PHP代码
- $tv1 = new Television();
- $tv2 = clone $tv1;
- $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
- $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
- echo $tv1; //tv1的object id 为 #1
-
echo '
'; - echo $contr1; //contr1的object id 为#2
-
echo '
'; - echo $tv2; //tv2的object id 为 #3
-
echo '
'; - echo $contr2; //contr2的object id 为#2
经过复制之后,我们查看对象id,通过clone操作从tv1复制出了tv2,tv1和tv2的对象id分别是1和3,这表示tv1和tv2是引用两个不同的电视机对象,这符合clone操作的结果。然后我们分别获取了tv1的遥控器对象contr1和tv2的遥控器对象contr2,通过查看它们的对象id我们发现contr1和contr2的对象id都是2,这表明它们是到同一个对象的引用,也就是说我们虽然从tv1复制出tv2,但是遥控器并没有被复制,每台电视机都应该配有一个遥控器,而这里tv2和tv1共用一个遥控器,这显然是不合常理的。
由此可见,clone操作有这么一个非常大的缺陷:使用clone操作复制对象时,当被复制的对象有对其它对象的引用的时候,引用的对象将不会被复制。然而这种情况又非常的普遍,现今 “合成/聚合复用”多被提倡用来代替“继承复用”,“合成”和“聚合”就是让一个对象拥有对另一个对象的引用,从而复用被引用对象的方法。我们在使用clone的时候应该考虑到这样的情况。那么在clone对象的时候我们应该如何去解决这样的一个缺陷呢?可能你很快就想到了之前提到的__clone魔术方法,这确实是一种解决方案。
方案1:用__clone魔术方法弥补
前面我们已经介绍了__clone魔术方法的用法,我们可以在__clone方法中将被复制对象中其它对象的引用重新引用到一个新的对象。下面我们看看修改后的__clone()魔术方法:
PHP代码
- public function __clone()
- {
- $this->setIdentity(0);
- //重新设置一个遥控器对象
- $this->setControl(new Telecontrol());
- }
第04行中我们为复制出来的电视机对象重新设置了一个遥控器,我们按照之前的方法查看对象的id可以发现,两台电视机的遥控器拥有不同的对象id,这样我们的问题就解决了。
但是这样的方式大概并不算太好,如果被复制对象中有多个到其它对象的引用,我们必须在__clone方法中逐个的重新设置,更糟糕的是如果被复制对象的类由第三方提供,我们无法修改代码,那复制操作基本就无法顺利完成了。
我们使用clone来复制对象,这种复制叫做“浅复制”:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。也就是说,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。相对于“浅复制”,当然也有一个“深复制”:被复制的对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。也就是说,深复制把要复制的对象所引用的对象都复制了一遍。深复制需要决定深入到多少层,这是一个不容易确定的问题,此外可能会出现循环引用的问题,这些都必须小心处理。我们的方案2将是一个深复制的解决方案。
方案2:利用串行化做深复制
PHP有串行化(serialize)和反串行化(unserialize)函数,我们只需要用serialize()将一个对象写入一个流,然后从流中读回对象,那么对象就被复制了。在JAVA语言里面,这个过程叫做“冷藏”和“解冻”。下面我们将测试一下这个方法:
PHP代码
- $tv1 = new Television();
- $tv2 = unserialize(serialize($tv1));//序列化然后反序列化
- $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
- $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
- echo $tv1; //tv1的object id 为 #1
-
echo '
'; - echo $contr1; //contr1的object id 为#2
-
echo '
'; - echo $tv2; //tv2的object id 为 #4
-
echo '
'; - echo $contr2; //contr2的object id 为#5
我们可以看到输出结果,tv1和tv2拥有了不同的遥控器。这比方案1要方便很多,序列化是一个递归的过程,我们不需要理会被对象内部引用了多少个对象以及引用了多少层对象,我们都可以彻底的复制。注意使用此方案时我们无法触发__clone魔术方法来完成一些附加操作,当然我们可以在深复制之后再进行一次clone操作来触发__clone魔术方法,只是会对效率些小的影响。另外此方案会触发被复制对象和所有被引用对象的__sleep和__wakeup魔术方法,所以这些情况都需要被考虑。
总结
不同的对象复制方式有着不同的效果,我们应该根据具体应用需求来考虑使用哪种方式以及如何改进复制方式。PHP5的面向对象特性和JAVA比较接近,相信我们可以从JAVA中借鉴很多宝贵的经验

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

我們用戶們在使用這款平台的時候應該都能夠了解到上面對於一些功能的多樣性,我們知道一些歌曲的歌詞都寫的非常的不錯。有時候甚至都會多聽幾遍,覺得其中的含義都是非常深刻的,所以我們想要去了解其中的勝意,就想要直接的複製下來當文案來使用,不過對於要使用的話,還是要學會如何去複製歌詞才可以,這些操作方面我相信大家們應該都並不模式,但是在手機上面操作確實是有點難度,所以為了能夠讓大家們更好的了解的話,今日小編就來為你們好好的講解上面的一些操作體驗,如果你們也喜歡的話,就和小編一起來看看吧,不要錯過了。

StableDiffusion3的论文终于来了!这个模型于两周前发布,采用了与Sora相同的DiT(DiffusionTransformer)架构,一经发布就引起了不小的轰动。与之前版本相比,StableDiffusion3生成的图质量有了显著提升,现在支持多主题提示,并且文字书写效果也得到了改善,不再出现乱码情况。StabilityAI指出,StableDiffusion3是一个系列模型,其参数量从800M到8B不等。这一参数范围意味着该模型可以在许多便携设备上直接运行,从而显著降低了使用AI

這篇論文探討了在自動駕駛中,從不同視角(如透視圖和鳥瞰圖)準確檢測物體的問題,特別是如何有效地從透視圖(PV)到鳥瞰圖(BEV)空間轉換特徵,這一轉換是透過視覺轉換(VT)模組實施的。現有的方法大致分為兩種策略:2D到3D和3D到2D轉換。 2D到3D的方法透過預測深度機率來提升密集的2D特徵,但深度預測的固有不確定性,尤其是在遠處區域,可能會引入不準確性。而3D到2D的方法通常使用3D查詢來採樣2D特徵,並透過Transformer學習3D和2D特徵之間對應關係的注意力權重,這增加了計算和部署的

軌跡預測在自動駕駛中承擔著重要的角色,自動駕駛軌跡預測是指透過分析車輛行駛過程中的各種數據,預測車輛未來的行駛軌跡。作為自動駕駛的核心模組,軌跡預測的品質對於下游的規劃控制至關重要。軌跡預測任務技術堆疊豐富,需熟悉自動駕駛動/靜態感知、高精地圖、車道線、神經網路架構(CNN&GNN&Transformer)技能等,入門難度很高!許多粉絲期望能夠盡快上手軌跡預測,少踩坑,今天就為大家盤點下軌跡預測常見的一些問題和入門學習方法!入門相關知識1.預習的論文有沒有切入順序? A:先看survey,p

23年9月國防科大、京東和北理工的論文「DeepModelFusion:ASurvey」。深度模型整合/合併是一種新興技術,它將多個深度學習模型的參數或預測合併為一個模型。它結合了不同模型的能力來彌補單一模型的偏差和錯誤,以獲得更好的性能。而大規模深度學習模型(例如LLM和基礎模型)上的深度模型整合面臨一些挑戰,包括高運算成本、高維度參數空間、不同異質模型之間的干擾等。本文將現有的深度模型融合方法分為四類:(1)“模式連接”,透過一條損失減少的路徑將權重空間中的解連接起來,以獲得更好的模型融合初

寫在前面&筆者的個人理解基於圖像的3D重建是一項具有挑戰性的任務,涉及從一組輸入圖像推斷目標或場景的3D形狀。基於學習的方法因其直接估計3D形狀的能力而受到關注。這篇綜述論文的重點是最先進的3D重建技術,包括產生新穎的、看不見的視野。概述了高斯飛濺方法的最新發展,包括輸入類型、模型結構、輸出表示和訓練策略。也討論了尚未解決的挑戰和未來的方向。鑑於該領域的快速進展以及增強3D重建方法的眾多機會,對演算法進行全面檢查似乎至關重要。因此,本研究對高斯散射的最新進展進行了全面的概述。 (大拇指往上滑

將MySQL查詢結果陣列轉換為物件的方法如下:建立一個空物件陣列。循環結果數組並為每一行建立一個新的物件。使用foreach迴圈將每一行的鍵值對賦給新物件的對應屬性。將新物件加入到物件數組中。關閉資料庫連線。

PHP函數可以透過使用return語句後接物件實例來傳回對象,從而將資料封裝到自訂結構中。語法:functionget_object():object{}。這允許創建具有自訂屬性和方法的對象,並以對象的形式處理資料。
