php的深拷贝和浅拷贝问题,普通变量对象的赋值 =
是深拷贝,&
是浅拷贝。
$a = 1;
$b = $a;
$b = 2;
$c = &$b;
$c = 3;
echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
输出结果:
1
3
3
类对象的拷贝问题就得看下php版本了。php4时类对象的赋值=
就是一次深拷贝。php5时类对象的赋值=
就是一次浅拷贝。下面的代码是在php5.6.29
的环境上运行的
<?php
// php版本是5.6.29
class CopyObj {
public $arg;
public function __construct($arg) {
$this->arg = $arg;
}
public function setArg($sa) {
$this->arg = $sa;
}
public function getArg() {
return $this->arg;
}
}
$co = new CopyObj('create obj co');
$cl = $co;
$cl->setArg('create obj cl');
echo $co->getArg() . PHP_EOL;
var_dump(xdebug_debug_zval('co'));
输出结果:
create obj cl
co: (refcount=2, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj cl' }
从打印的xdebug_debug_zval信息来看,co
和cl
是指向同一个zval
.既然赋值=
操作都是引用,那&
就更不用提了,那问题来了,如何做深拷贝呢?世界上最好的语言已经帮我们考虑到了这些事情,clone()
就是它。码农的世界里面简单粗暴最有效,上代码:
<?php
class CopyObj {
public $arg;
public function __construct($arg) {
$this->arg = $arg;
}
public function setArg($sa) {
$this->arg = $sa;
}
public function getArg() {
return $this->arg;
}
}
$co = new CopyObj('create obj co');
$cl = clone($co);
$cl->setArg('create obj cl');
var_dump(xdebug_debug_zval('co'));
var_dump(xdebug_debug_zval('cl'));
输出结果:
co: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj co' }
cl: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj cl' }
从zval
信息上可以看出是co
和cl
是独立的个体。但往往事情没有我们想像的那么简单,因为A
类对象里面还可以包含B
类对象的信息。看demo
:
<?php
class CoBi {
public $biarg = 1;
}
class CopyObj {
public $arg;
public $cobi;
public function __construct($arg) {
$this->arg = $arg;
$this->cobi = new CoBi();
}
public function setArg($sa) {
$this->arg = $sa;
}
public function getArg() {
return $this->arg;
}
}
$cj = new CoBi();
$co = new CopyObj('create obj co');
$cl = clone($co);
$cl->setArg('create obj cl');
var_dump(xdebug_debug_zval('co'));
var_dump(xdebug_debug_zval('cl'));
var_dump(xdebug_debug_zval('cj'));
输出结果:
co: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj co'; public $cobi = (refcount=2, is_ref=0)=class CoBi { public $biarg = (refcount=3, is_ref=0)=1 } }
cl: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj cl'; public $cobi = (refcount=2, is_ref=0)=class CoBi { public $biarg = (refcount=3, is_ref=0)=1 } }
cj: (refcount=1, is_ref=0)=class CoBi { public $biarg = (refcount=3, is_ref=0)=1 }
简单分析下输出的结果就知道cj
的引用计数是3
,所以说clone()
函数只能帮你完成普通成员变量的深拷贝,但对象成员变量还是浅拷贝。这下怎么玩?难道没有办法完成深拷贝了吗?php
还提供了一个__clone()
函数,这个函数只有在clone对象的时候才会被调用,如果类对象不显示定义__clone
,那么php直接使用默认的,复制类对象的普通成员变量。如果要做到完全的深拷贝,可以显示定义该函数,在该函数里面做一次clone
操作。
<?php
class CoBi {
public $biarg = 1;
}
class CopyObj {
public $arg;
public $cobi;
public function __construct($arg) {
$this->arg = $arg;
$this->cobi = new CoBi();
}
public function setArg($sa) {
$this->arg = $sa;
}
public function getArg() {
return $this->arg;
}
public function __clone(){
$this->cobi = clone($this->cobi);
}
}
$co = new CopyObj('create obj co');
$cl = clone($co);
$cl->setArg('create obj cl');
echo $co->getArg() . PHP_EOL;
echo $cl->getArg() . PHP_EOL;
var_dump(xdebug_debug_zval('co'));
var_dump(xdebug_debug_zval('cl'));
输出结果:
co: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj co'; public $cobi = (refcount=1, is_ref=0)=class CoBi { public $biarg = (refcount=3, is_ref=0)=1 } }
cl: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj cl'; public $cobi = (refcount=1, is_ref=0)=class CoBi { public $biarg = (refcount=3, is_ref=0)=1 } }
结果看,$cobi
对象的refcount
是1
。没问题,__clone
函数能帮我们解决问题。但如果A
类含有的类对象很多,那得一个个地写上clone
。Golang是通过序列化和反序列化来完成这个深拷贝的,php
必须也可以:
<?php
class CoBi {
public $biarg = 1;
}
class CopyObj {
public $arg;
public $cobi;
public function __construct($arg) {
$this->arg = $arg;
$this->cobi = new CoBi();
}
public function setArg($sa) {
$this->arg = $sa;
}
public function getArg() {
return $this->arg;
}
}
$co = new CopyObj('create obj co');
$cl = serialize($co);
$cl = unserialize($cl);
$cl->setArg('create obj cl');
echo $co->getArg() . PHP_EOL;
echo $cl->getArg() . PHP_EOL;
var_dump(xdebug_debug_zval('co'));
var_dump(xdebug_debug_zval('cl'));
输出结果:
create obj co
create obj cl
co: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj co'; public $cobi = (refcount=1, is_ref=0)=class CoBi { public $biarg = (refcount=2, is_ref=0)=1 } }
cl: (refcount=1, is_ref=0)=class CopyObj { public $arg = (refcount=1, is_ref=0)='create obj cl'; public $cobi = (refcount=1, is_ref=0)=class CoBi { public $biarg = (refcount=1, is_ref=0)=1 } }
结果看,$cobi
对象的refcount
也是1
。序列化和反序列化也可以解决这个问题。欢迎大家在评论区给出批评和指正以及其他语言的深拷贝做法。
有疑问加站长微信联系(非本文作者)