变量有值传递和引用传递,其实,对象也如此,所以,对象有浅克隆和深克隆这么一说。浅克隆也叫浅复制或浅拷贝,深克隆也叫深复制或深拷贝。唉,名称复名称,名称何其多?
浅克隆
引用的解释,官方说的最靠谱
警告
自 PHP 5
起,new
运算符自动返回一个引用
浅克隆在赋值时,引用赋值,相当于取了一个别名。对其中一个的修改,会影响到另一个
赋值
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
| <?php
class Student
{
private $name;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
}
$jack1 = new Student();
$jack1->setName('Jack 1');
$jack2 = $jack1;
$jack2->setName('Jack 2');
var_dump($jack1);
var_dump($jack2);
// 结果
class Student#1 (1) {
private $name =>
string(6) "Jack 2"
}
class Student#1 (1) {
private $name =>
string(6) "Jack 2"
}
|
在以上的场景中,无论你修改哪个 Jack
,都会影响到另一个对象,当然我这样说,可能有些人不服,比如:
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
| <?php
class Student
{
private $name;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
}
$jack1 = new Student();
$jack1->setName('Jack 1');
var_dump($jack1); // 这儿调整了下执行顺序
$jack2 = $jack1;
$jack2->setName('Jack 2');
var_dump($jack2);
// 结果
class Student#1 (1) {
private $name =>
string(6) "Jack 1"
}
class Student#1 (1) {
private $name =>
string(6) "Jack 2"
}
|
看到了吗?两个 Jack
的学生名称就是不一样。感觉像是被打脸了,如果你再 $jack2
赋值后再打印一下 $jack1
试试,看谁能笑到最后
克隆
警告
使用 clone
操作复制对象时,当被复制的对象有对其它对象的引用的时候,引用的对象将不会被复制
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
| <?php
class Student
{
private $name;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
}
class Administration
{
private $classLeader;
public function setClassLeader(Student $student)
{
$this->classLeader = $student;
return $this;
}
/**
* @return \Student
*/
public function getClassLeader(): Student
{
return $this->classLeader;
}
}
$jack = new Student();
$jack->setName('Jack');
$admin1 = new Administration();
$admin1->setClassLeader($jack);
$admin2 = clone $admin1;
$admin2->getClassLeader()->setName('Lucy');
echo $admin1->getClassLeader()->getName(), PHP_EOL, $admin2->getClassLeader()->getName();
// 结果
Lucy
Lucy
|
操蛋呀,$admin1->getClassLeader()
和 $admin2->getClassLeader()
居然都指向同一个对象。但 $admin1
和 $admin2
都是货真价实的两个独立对象,唯一的遗憾是他们的属性出卖了他们,看来这也是个浅复制
所以,浅克隆时,被赋值对象的所有变量都还与原来对象有相同的值,而所有的对其他对象的引用都仍然指向原来的对象
深克隆
方案一
基于魔术变量 __clone()
的实现
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
| <?php
class Student
{
private $name;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
}
class Administration
{
private $classLeader;
public function __clone()
{
$this->classLeader = clone $this->classLeader;
}
public function setClassLeader(Student $student)
{
$this->classLeader = $student;
return $this;
}
/**
* @return \Student
*/
public function getClassLeader(): Student
{
return $this->classLeader;
}
}
$jack = new Student();
$jack->setName('Jack');
$admin1 = new Administration();
$admin1->setClassLeader($jack);
$admin2 = clone $admin1;
$admin2->getClassLeader()->setName('Lucy');
print $admin1->getClassLeader()->getName();
print PHP_EOL;
print $admin2->getClassLeader()->getName();
// 结果
Jack
Lucy
|
方案二
利用串行化做深复制
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
| <?php
class Student
{
private $name;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
}
class Administration
{
private $classLeader;
public function setClassLeader(Student $student)
{
$this->classLeader = $student;
return $this;
}
/**
* @return \Student
*/
public function getClassLeader(): Student
{
return $this->classLeader;
}
}
$jack = new Student();
$jack->setName('Jack');
$admin1 = new Administration();
$admin1->setClassLeader($jack);
/** @var \Administration $admin2 */
$admin2 = unserialize(serialize($admin1));
$admin2->getClassLeader()->setName('Lucy');
print_r([
$admin1->getClassLeader()->getName(),
$admin2->getClassLeader()->getName(),
]);
// 结果
Array
(
[0] => Jack
[1] => Lucy
)
|
当然,利用串行化的这种方案也可以封装到 __clone()
方法中:
1
2
3
4
5
6
7
8
| public function __clone()
{
foreach($this as $key => $val) {
if (is_object($val) || (\is_array($val))) {
$this->{$key} = unserialize(serialize($val));
}
}
}
|
所以,深克隆把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。