Perl变量的复制
2016-03-22 14:33:48 阿炯

Perl里面没有提供复制数据结构的函数,如果需要一份列表的的匿名副本,可以把列表放在匿名列表构造操作符中,然后对它进行解引用。本文着重介绍对复杂数据结构(引用)的复制,如浅拷贝、深拷贝的异同。

用@{[]} 或 eval{} 的形式来复制列表

小心使用{}
my @a = @{func_returning_arrayref} ; #错误
my @a = @{func_returning_arrayref()} ; #正确
my @a=  @{&func_returning_arrayref}; #正确
my @a=  @{+func_returning_arrayref}; #正确

my @missing_h = grep {  s/\c$/\.h/ and ! -e } @{[@cfiles]};

另一种产生临时副本的方法是把它放在eval块中,它能返回块内部最后一个表达式的计算结果。
my @missing_h = eval {@cfiles};

提示:尽量使用eval方式,比前一种方式更加高效。

不过遇到这种情况还应该考虑是否真正需要临时副本。 在前面的例子中,完全可以在grep做些改进,引入一个$_变量的临时副本就可以了。
my @missing_h = grep {  my $h= $_; $h =~  s/\c$/\.h/ and !  ( -e $h)  }  @cfiles;
 
以上方法都是“影子副本”既完整的独立的副本。如果原始列表中的元素是引用的话,复制的结果和原始数据共享数据。如果确定需要一份完全独立的深层副本,请使用Storable模块的 dclone 函数:
use Storable qw(dclone);
my @copy_ref = dclone(\@array);

下面来详述:
Perl hash reference 的浅拷贝

最简单复制:
my %copy = %$hash;

If you want another reference, just expand the original reference in the anonymous hash constructor:

如果你想要另一个引用(浅拷贝),只是扩大原引用匿名散列构造:
my $copy = { %$hash };

For those wondering about shallow copies: this sort of assignment only makes new values for the top level keys.Any values that are references will still be the same reference in the new hash. That is, you can replace a value in the new hash without changing the original hash. If you merely change the value, such as pushing a new item onto an anonymous array value, both hashes get the change because they share the same reference. As such, shallow copies are usually not what you want.

对于那些关于浅拷贝:这种类型的作业只会成为新的顶级键值。在新散列引用中的任何值仍然是相同的。也就是说,你可以修改新散列中的值而不会改变原来的散列键值,反之亦然。如push一个新的键包含一个匿名数组的值,这两个散列都将会改变,因为它们共享相同的引用。因此,浅副本通常不是你想要的。

Copying a hashref in Perl

When you copy a hashref into another scalar, you are copying a reference to the same hash. This is similar to copying one pointer into another, but not changing any of the pointed-to memory.

当复制一个hashref到另一个标量,实际上复制的是一个相同散列的引用。这是类似于一个指针复制到另一个,但不改变任何的内存的指针。

You can create a shallow copy of a hash easily:

可以很容易创建一个浅拷贝的哈希引用:
my $copy = { %$source };

The %$source bit in list context will expand to the list of key value pairs. The curly braces (the anonymous hash constructor) then takes that list an creates a new hashref out of it. A shallow copy is fine if your structure is 1-dimensional, or if you do not need to clone any of the contained data structures.

%$source在列表上下文将会扩大到键值对列表。花括号(匿名散列构造函数),那么将为这个列表的创建一个新的hashref。浅拷贝是好的如果是单维结构或者如果不需要克隆任何包含于其中的数据结构,即简单不复制的数据结构。

use Data::Dumper;
my %a=('a'=>1,'b'=>2,'c'=>3,'d'=>{'z'=>9,'y'=>8});
#my %b={%$a};
my %b=%a;
say Dumper(\%a,\%b);
$b{'b'}=5;
say 'Changed here:';
say Dumper(\%a,\%b);

########################

use Data::Dumper;
my $a={'a'=>1,'b'=>2,'c'=>3,'d'=>{'z'=>9,'y'=>8}};
#my $b={%$a};
my $b=$a;
say Dumper($a,$b);
$b->{'b'}=5;
say 'Changed here:';
say Dumper($a,$b);

可以看出上面示例$a中的'd'键下的结构不能正常显示出来,但访问其中的值还是可以的。第二个示例中修改$b的键还会将$a也一同修改了,下面是浅拷贝的示例:

use Data::Dumper;
my $a={'a'=>1,'b'=>2,'c'=>3,'d'=>{'z'=>9,'y'=>8}};
my $b={%$a};
#say Dumper($a,$b);
say $a->{'d'}->{'y'};
#$a->{'d'}->{'y'}=88;
$b->{'d'}->{'y'}=88;
say 'Changed here:';
#say Dumper($a,$b);
say $a->{'d'}->{'y'};
say $b->{'d'}->{'y'};

可以看到hash of hash的键值('d'),无论改变哪一个,都会影响这两个变量值。

To do a full deep copy, you can use the core module Storable.

做一个完整的深拷贝,可以使用核心模块Storable。多用于其中还包括了其它数据结构(hash of array,hash of hash等),且两者之间不再有任何关系。

use Storable 'dclone';
use Data::Dumper;
my $a={'a'=>1,'b'=>2,'c'=>3,'d'=>{'z'=>9,'y'=>8}};
#my %b={%$a};
my $b=dclone $a;
say Dumper($a,$b);
$b->{'b'}=5;
say 'Changed here:';
say Dumper($a,$b);

通过匿名散列创建hash结构
my $student = {
 last => 'Freeoa',
 first => 'John',
 bday  => '01/08/72'
};

$student->{last} = 'Freeoa';

匿名数组构造器返回的是一个引用,而不是列表。这个匿名数组构造器的目的,就是允许直接创建指向数组对象的引用,且无需实现创建一个具名数组。

操作符和函数会创建列表或标量上下文。匿名数组构造器是操作符,而圆括号不是。仅仅用圆括号将某些东西括起来是不会把标量上下文环境转换成列表上下文环境的。

关于浅拷贝和深度拷贝(本节转自骏马金龙的博客)

深、浅拷贝的概念:
浅拷贝:shallow copy,只拷贝第一层的数据。Perl中赋值操作就是浅拷贝
深拷贝:deep copy,递归拷贝所有层次的数据,Perl中Clone模块的clone方法,以及Storable的dclone()函数是深拷贝

所谓第一层次,指的是出现嵌套的复杂数据结构时,那些引用指向的数据对象属于深一层次的数据。例如:
@Person1=('wugui','tuner');
@Person2=('longshuai','xiaofang',['wugui','tuner']);
@Person3=('longshuai','xiaofang',\@Person1);

@Person1只有一层深度,@Person2和@Person3都有两层深度。

浅拷贝

当进行赋值时,拷贝给目标的只是第一层数据对象。
@Person1_shallow=@Person1;
@Person2_shallow=@Person2;
@Person3_shallow=@Person3;

拷贝给@Person1_shallow的是整个@Person1数据,它们在结果上完全等价,拷贝给@Person{2,3}_shallow的是@Person{2,3}的第一层数据,也就是两个元素和一个引用,拷贝引用时不会对引用进行递归拷贝给赋值对象。简单地说,浅拷贝的过程就是源数据是怎样的,拷贝后就是怎样的。

如下图:


因为浅拷贝时引用不会进行递归,所以拷贝前后的两个对象都指向同一个引用。所以修改它们共同引用的数据,同时也会修改另一份数据。例如:
@Person=('longshuai','xiaofang',['wugui','tuner']);
@Person_shallow=@Person;

$Person_shallow[2][1]="fairy";

say "$Person_shallow[2][1]";    # 输出:fairy
say "$Person[2][1]";    # 输出:fairy

上述例子中只是给@Person_shallow进行了元素修改操作,但同时却把原始数据@Person也改了。因为@Person_shallow和@Person引用的数据对象是相同的。

深拷贝:Clone和Storable

为了在拷贝阶段保护引用的数据对象,Perl提供了深拷贝的方式。它会对引用进行递归,拷贝引用所指向的数据对象。如下图:


也就是说,深拷贝的方式使得拷贝前后出现了两个完全独立的、互不影响的数据对象,修改其中任何一个对象都不会影响另一个。

Perl提供了一个Clone模块,它的clone()可以递归拷贝,也就是深拷贝。Storable模块也提供了dclone()函数进行深拷贝。在拷贝层次低于或等于3层时,Clone()的拷贝速度更快,在层次大于或等于4层时,dclone()速度更快。

需要注意,clone()和dclone()的参数都必须是引用变量,当然也可以是\构建的引用。例如使用Clone的clone()方法进行深拷贝:
shell> cpan -i Clone
use 5.10;
use Clone qw(clone);

@Person=('longshuai','xiaofang',['wugui','tuner']);
$Person_deep=clone(\@Person);

$Person[2][1]="fairy";

say "$Person[2][1]";    # 输出:fairy
say "$Person_deep->[2][1]";    # 输出:tuner

同样,使用Storable模块的dclone()。

use 5.10;
use Storable qw(dclone);

@Person=('longshuai','xiaofang',['wugui','tuner']);
$Person_deep=dclone(\@Person);

$Person[2][1]="fairy";

say "$Person[2][1]";    # 输出:fairy
say "$Person_deep->[2][1]";    # 输出:tuner