PHP之剑走偏锋的DeepCopy

ieasy_tm · · 1867 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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信息来看,cocl是指向同一个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信息上可以看出是cocl是独立的个体。但往往事情没有我们想像的那么简单,因为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对象的refcount1。没问题,__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。序列化和反序列化也可以解决这个问题。欢迎大家在评论区给出批评和指正以及其他语言的深拷贝做法。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:ieasy_tm

查看原文:PHP之剑走偏锋的DeepCopy

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1867 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传