我有一个对象数组。我知道对象由“引用”分配,数组由“值”分配。但是当我分配数组时,数组的每个元素都引用了对象,所以当我修改任一数组中的对象时,更改会反映在另一个数组中。
有没有一种简单的方法来克隆一个数组,或者我必须循环它来克隆每个对象?
复制数组时,已复制对相同对象的引用。但听起来你想要 浅拷贝 在创建第二个数组时深度复制第一个数组中引用的对象,因此您将获得两个不同但相似的对象数组。
我现在能够提出的最直观的方法是循环;那里可能有更简单或更优雅的解决方案:
$new = array();
foreach ($old as $k => $v) {
$new[$k] = clone $v;
}
默认情况下,对象通过指向传递,并且并不总是易于克隆,因为它们可能具有循环引用。您可能更适合使用不同的数据结构选择。
对于那些提供浅拷贝解决方案的人来说,更简单的方法是:
$b = (array)$a;
对于深层副本,我不建议使用此解决方案:
$ nuarr = json_decode(json_encode($ array));
这是一个深层复制。它只支持PHP类型的一个子集,并将对象交换到数组或数组,这些对象可能不是你想要的,也可能是破坏二进制值等等。
如果为深度副本创建手动递归函数,则标量值和键之后的内存使用量会更少,因此使用json或任何序列化程序会超出其执行点的影响。
如果性能不是对诸如对象之类的东西有更广泛支持的关注点,那么对深度拷贝使用反序列化(序列化($ a))可能更好。尽管如果它打破循环引用和其他一些不寻常的事情我也不会感到惊讶。
array_merge_recursive或array_walk_recursive也可用于数组。
您可以轻松创建自己的递归函数,该函数使用is_object和is_array来选择适当的复制方法。
对于PHP 5及以上版本,可以使用ArrayObject
cunstructur来克隆数组,如下所示:
$myArray = array(1, 2, 3);
$clonedArray = new ArrayObject($myArray);
$array = array_merge(array(), $myArray);
您需要克隆对象以避免引用同一对象。
function array_copy($arr) {
$newArray = array();
foreach($arr as $key => $value) {
if(is_array($value)) $newArray[$key] = array_copy($value);
else if(is_object($value)) $newArray[$key] = clone $value;
else $newArray[$key] = $value;
}
return $newArray;
}
正如AndreKR所建议的,如果你已经知道你的数组包含对象,那么使用array_map()是最好的方法:
$clone = array_map(function ($object) { return clone $object; }, $array);
我也选择了克隆。克隆数组不起作用(你可以考虑一些arrayaccess实现为你这样做),所以对于array_map的数组克隆:
class foo {
public $store;
public function __construct($store) {$this->store=$store;}
}
$f = new foo('moo');
$a = array($f);
$b = array_map(function($o) {return clone $o;}, $a);
$b[0]->store='bar';
var_dump($a, $b);
如果您的对象支持序列化,您甚至可以通过浏览其睡眠状态和返回来进行深度浅拷贝/克隆:
$f = new foo('moo');
$a = array($f);
$b = unserialize(serialize($a));
$b[0]->store='bar';
var_dump($a, $b);
然而,这可能有点冒险。
你需要循环它(可能使用像array_map()
这样的函数),没有PHP函数来自动执行数组的深层副本。
我这样做了:
function array_clone($array) {
array_walk_recursive($array, function(&$value) {
if(is_object($value)) {
$value = clone $value;
}
});
return $array;
}
函数arg复制数组而不克隆对象,然后克隆每个嵌套对象。因此,如果算法未在函数内部使用,则无法工作。
请注意,此函数以递归方式克隆数组。如果您不希望发生这种情况,可以使用array_walk
而不是array_walk_recursive
。
这是我对一系列对象和克隆的最佳实践。通常,对于在数组中使用的每个对象(或接口)类都有一个Collection类是个好主意。随着魔术功能__clone
克隆成为一个正式的例程:
class Collection extends ArrayObject
{
public function __clone()
{
foreach ($this as $key => $property) {
$this[$key] = clone $property;
}
}
}
要克隆您的数组,请将其用作Collection,然后克隆它:
$arrayObject = new Collection($myArray);
$clonedArrayObject = clone $arrayObject;
更进一步,您应该为您的类和每个子类添加克隆方法。这对深度克隆很重要,或者您可能会产生意想不到的副作用:
class MyClass
{
public function __clone()
{
$this->propertyContainingObject = clone $this->propertyContainingObject;
}
}
关于使用ArrayObject的一个重要注意事项是,您不能再使用is_array()
了。因此,请在重构代码时注意这一点。
或者也
$nuarr = json_decode(json_encode($array));
但它很贵,我更喜欢Sebastien版本(array_map)