php如何复制数组

php5对象复制、clone、浅复制与深复制实例详解

本文实例讲述了php5对象复制、clone、浅复制与深复制。分享给大家供大家参考,具体如下:

对象复制的由来

为什么对象会有“复制”这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码

PHP代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • /**
  • * 电视机类
  • */
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
  • echo '<br>';
  • echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
  • echo '<br>';
  • //把tv2涂成白色
  • $tv2->setColor('white');
  • echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
  • echo '<br>';
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • $tv1 = new Television();
  • $tv2 = clone $tv1;
  • echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
  • echo '<br>';
  • echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
  • echo '<br>';
  • //把tv2换成涂成白色
  • $tv2->setColor('white');
  • echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
  • echo '<br>';
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • /**
  • 电视机类
  • */
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • $tv1 = new Television();
  • $tv1->setIdentity('111111');
  • echo 'id of tv1 is ' . $tv1->getIdentity();//111111
  • echo '<br>';
  • $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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • /**
  • 电视机类
  • */
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • $tv1 = new Television();
  • $tv2 = clone $tv1;
  • $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
  • $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
  • echo $tv1//tv1的object id 为 #1
  • echo '<br>';
  • echo $contr1; //contr1的object id 为#2
  • echo '<br>';
  • echo $tv2//tv2的object id 为 #3
  • echo '<br>';
  • 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代码

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • public function __clone()
  • {
  •   $this->setIdentity(0);
  •   //重

    猜您喜欢