关于在Perl中正确使用解除引用的困惑

时间:2011-07-18 22:15:50

标签: perl reference performance

前几天我注意到 - 在更改散列中的值时 - 当您在Perl中取消引用散列时,实际上您正在制作该散列的副本。为了确认我写了这个快速的小脚本:

#! perl
use warnings;
use strict;

my %h = ();
my $hRef = \%h;
my %h2 = %{$hRef};
my $h2Ref = \%h2;

if($hRef eq $h2Ref) {
  print "\n\tThey're the same $hRef $h2Ref";
}
else {
  print "\n\tThey're NOT the same $hRef $h2Ref";
}
print "\n\n";

输出:

    They're NOT the same HASH(0x10ff6848) HASH(0x10fede18)

这让我意识到我的一些脚本中可能存在一些不符合预期的行为。为什么它首先就是这样?如果你传递或返回一个哈希,那么假设取消引用哈希将允许我改变被解除引用的哈希的值是更自然的。相反,我只是在整个地方制作副本而没有任何真正的需要/理由超出使语法更明显。

我意识到这个事实,我甚至没有注意到这一点,直到现在显示它可能没有那么大的交易(就需要修复我的所有脚本而言 - 但重要的是前进)。我认为很难看到明显的性能差异,但这并没有改变我仍然感到困惑的事实。

这是perl的设计吗?是否有一些我不知道的明确原因;或者这只是知道而你 - 作为程序员 - 期望相应地知道和编写脚本吗?

4 个答案:

答案 0 :(得分:14)

问题是你要在这一行中使用散列的副本:

my %h2 = %{$hRef};

这是可以理解的,因为SO上的很多帖子使用这个成语为哈希创建一个本地名称,而没有解释它实际上正在制作副本。

在Perl中,哈希是一个复数值,就像一个数组。这意味着在列表上下文中(例如,在分配给哈希时获得),聚合被分解为其内容列表。然后将这个对列表组装成一个新的哈希,如图所示。

您想要做的是直接使用参考。

for (keys %$hRef) {...}
for (values %$href) {...}

my $x = $href->{some_key};
# or
my $x = $$href{some_key};

$$href{new_key} = 'new_value';

使用普通哈希时,在讨论整个哈希时,你有一个%,在讨论单个元素时有$,而@时谈论一片。然后,这些符号中的每一个都跟有一个标识符。

 %hash          # whole hash
 $hash{key}     # element
 @hash{qw(a b)} # slice

要使用名为$href的引用,只需将上述代码中的字符串hash替换为$href即可。换句话说,$href是标识符的完整名称:

%$href          # whole hash
$$href{key}     # element
@$href{qw(a b)} # slice

每一个都可以用更详细的形式写成:

%{$href}
${$href}{key}
@{$href}{qw(a b)}

这又是'$href'字符串'hash'的替换,作为标识符的名称。

%{hash}
${hash}{key}
@{hash}{qw(a b)} 

使用元素时,您还可以使用解除引用箭头:

$hash->{key}  # exactly the same as $$hash{key}

但我更喜欢double sigil语法,因为它类似于整个聚合和切片语法,以及普通的非引用语法。

总而言之,无论何时你写这样的东西:

my @array = @$array_ref;
my %hash  = %$hash_ref;

您将复制每个聚合的第一级。直接使用解除引用语法时,您将处理实际值,而不是副本。


如果您想要哈希的REAL本地名称,但想要使用相同的哈希,则可以使用local关键字创建别名。

 sub some_sub {
    my $hash_ref = shift;
    our %hash; # declare a lexical name for the global %{__PACKAGE__::hash}
    local *hash = \%$hash_ref;
        # install the hash ref into the glob
        # the `\%` bit ensures we have a hash ref

    # use %hash here, all changes will be made to $hash_ref

 }  # local unwinds here, restoring the global to its previous value if any

这是纯粹的Perl混叠方式。如果您想使用my变量来保存别名,可以使用模块Data::Alias

答案 1 :(得分:7)

您正在混淆解除引用的操作,它本身并不创建副本,并且在列表上下文中使用哈希并分配该列表,这样做。 $hashref->{'a'}是一个取消引用,但肯定会影响原始哈希值。这也适用于$#$arrayrefvalues(%$hashref)

没有赋值,只有列表上下文%$hashref是一个混合的野兽;结果列表包含散列键的副本,但是包含实际散列值的别名。您可以看到这一点:

$ perl -wle'$x={"a".."f"}; for (%$x) { $_=chr(ord($_)+10) }; print %$x'
epcnal

VS

$ perl -wle'$x={"a".."f"}; %y=%$x; for (%y) { $_=chr(ord($_)+10) }; print %$x; print %y'
efcdab
epcnal

%$hashref%hash的行为没有任何不同。

答案 2 :(得分:5)

不,取消引用不会创建引用对象的副本。它是my创建一个新变量。

$ perl -E'
   my %h1; my $h1 = \%h1;
   my %h2; my $h2 = \%h2;
   say $h1;
   say $h2;
   say $h1 == $h2 ?1:0;
'
HASH(0x83b62e0)
HASH(0x83b6340)
0

$ perl -E'
   my %h;
   my $h1 = \%h;
   my $h2 = \%h;
   say $h1;
   say $h2;
   say $h1 == $h2 ?1:0;
'
HASH(0x9eae2d8)
HASH(0x9eae2d8)
1

不,$#{$someArrayHashRef}不会创建新数组。

答案 3 :(得分:0)

如果perl做了你的建议,那么变量很容易混淆,这将更加令人困惑。实际上,您可以使用globbing对变量进行别名,但是您需要明确地这样做。