使用\和{},[]在Perl中引用变量有什么区别?

3
下面的代码可以正常工作,但是如果我用push @array,\%hash代替push @array,{%hash}就不行了。请问有谁能帮助我理解其中的区别?我相信{%hash}指的是一个匿名哈希表。这是否意味着匿名哈希表的生命周期比对命名哈希表的引用(\%hash)更长呢?
use strict;
use warnings;
use Data::Dumper;
my @array;
my %hash;
%hash = ('a' => 1,
         'b' => 2,
         'c' => 3,);

push @array,{%hash};

%hash = ('e' => 1,
         'f' => 2,
         'd' => 3,);

push @array,{%hash};

print Dumper \@array;

输出

$VAR1 = [
          {
            'c' => 3,
            'a' => 1,
            'b' => 2
          },
          {
            'e' => 1,
            'd' => 3,
            'f' => 2
          }
        ];

更新 以下是我正在处理的实际代码。我认为在这种情况下,复制引用是唯一可能的解决方案。如果我错了,请纠正我。

use Data::Dumper;
use strict;
use warnings;

my %csv_data;
my %temp_hash;
my @cols_of_interest = qw(dev_file test_file diff_file status);
<DATA>; #Skipping the header
while (my $row = <DATA>) {
    chomp $row;
    my @array = split /,/,$row;
    @temp_hash{@cols_of_interest} = @array[3..$#array]; 
    push @{$csv_data{$array[0]}{$array[1] . ':' . $array[2]}},{%temp_hash};
}
print Dumper \%csv_data;

__DATA__
dom,type,id,dev_file,test_file,diff_file,status
A,alpha,1234,dev_file_1234_1.txt,test_file_1234_1.txt,diff_file_1234_1.txt,pass
A,alpha,1234,dev_file_1234_2.txt,test_file_1234_2.txt,diff_file_1234_2.txt,fail
A,alpha,1234,dev_file_1234_3.txt,test_file_1234_3.txt,diff_file_1234_3.txt,pass
B,beta,4567,dev_file_4567_1.txt,test_file_4567_1.txt,diff_file_4567_1.txt,pass
B,beta,4567,dev_file_4567_2.txt,test_file_4567_2.txt,diff_file_4567_2.txt,fail
C,gamma,3435,dev_file_3435_1.txt,test_file_3435_1.txt,diff_file_3435_1.txt,pass
D,hexa,6768,dev_file_6768_1.txt,test_file_6768_1.txt,diff_file_6768_1.txt,fail

1
请参考perldoc perlreftutperldoc perldsc ... Perl自带广泛且非常有用的文档。请使用它们。 - Sinan Ünür
3
在循环内部将%temp_hash定义为词法变量,然后使用常规引用会更有意义。你正在使用短但难以阅读的语法,这让你的生活变得更加困难,但必须费尽心思才能使其正常工作。如果列从不改变,只需执行 push @{$csv_data{$array[0]}}{...}, { dev_file => $array[3], test_file => $array[4] .. }。如果缩进得当,这种方法读起来更容易理解。 - simbabque
抱歉,但现在我又有些困惑了。在循环内部将%temp_hash变成词法作用域并使用常规引用确实可以工作,但是当我通过循环不断覆盖新值到%temp_hash中时,数据是如何存在的呢? - chidori
2个回答

13

两者都创建了 引用,但它们引用的是不同的东西。

\%hash 是对 %hash 的引用。如果取消引用,其值将随着 %hash 中的值而改变。

{%hash}%hash 中的值创建一个新的匿名哈希引用。它创建了一个副本。这是在Perl中创建数据结构浅拷贝的最简单方式。如果您更改 %hash,则此副本不受影响。


变量的生存时间与其类型或创建方式无关,只与作用域有关。在Perl中,引用是一个特殊情况,因为它有一个内部引用计数器来跟踪对值的引用,所以即使超出作用域,只要还有引用存在,它就会继续存在。这就是为什么下面的代码可以正常工作的原因:
sub frobnicate {
    my %hash = ( foo => 'bar' );
    return \%hash;
}

如果您想将引用与初始值分离,您需要通过弱引用将其转换为weaken,来自Scalar::Util。这样,引用计数不会受到影响,但仍然与值相关联,而副本则不会。

有关引用的更多信息,请参见perlrefperlreftut此问题涉及如何查看引用计数。对此的描述也可以在perlguts中的引用计数和死亡章节中找到。


3

你不能将\{}以及[]进行比较,因为它们完全不同。

{ LIST }my %anon = LIST; \%anon的简写。

[ LIST ]my @anon = LIST; \@anon的简写。


也许你想要比较的是

  1.  

    my %hash = ...;
    push @a, \%hash;
    
  2.  

    push @a, { ... };
    
  3.  

    my %hash = ...;
    push @a, { %hash };
    
第一个片段将%hash的引用放置在@a中。这在循环中被发现。只要在循环中找到my %hash,每次就会在@a中放置对新哈希的引用。
第二个片段使用匿名哈希完成同样的操作。
第三个片段复制了%hash并在@a中放置了对该副本的引用。它给人以浪费的印象,因此这种做法不被鼓励(它实际上并不是那么浪费,因为它允许重复使用%hash)。
你也可以编写你的代码。
# In reality, the two blocks below are probably the body of one sub or one loop.

{
   my %hash = (
      a => 1,
      b => 2,
      c => 3,
   );

   push @a, \%hash;
}

{
   my %hash = (
      d => 3,
      e => 1,
      f => 2,
   );

   push @a, \%hash;
}

或者

push @a, {
   a => 1,
   b => 2,
   c => 3,
};

push @a, {
   d => 3,
   e => 1,
   f => 2,
};

my @cols_of_interest = qw( dev_file test_file diff_file status );

my %csv_data;
if (defined( my $row = <DATA> )) {
    chomp $row;
    my @cols = split(/,/, $row);

    my %cols_of_interest = map { $_ => 1 } @cols_of_interest;
    my @cols_to_delete = grep { !$cols_of_interest{$_} } @cols;

    while ( my $row = <DATA> ) {
        chomp $row;
        my %row; @row{@cols} = split(/,/, $row);
        delete @row{@cols_to_delete};
        push @{ $csv_data{ $row{dev_file} }{ "$row{test_file}:$row{diff_file}" } }, \%row;
    }
}

更好的做法是使用一个合适的CSV解析器。
use Text::CSV_XS qw( );

my @cols_of_interest = qw( dev_file test_file diff_file status );

my $csv = Text::CSV_XS->new({
    auto_diag => 2,
    binary    => 1,
});

my @cols = $csv->header(\*DATA);

my %cols_of_interest = map { $_ => 1 } @cols_of_interest;
my @cols_to_delete = grep { !$cols_of_interest{$_} } @cols;

my %csv_data;
while ( my $row = $csv->getline_hr(\*DATA) ) {
    delete @$row{@cols_to_delete};
    push @{ $csv_data{ $row->{dev_file} }{ "$row->{test_file}:$row->{diff_file}" } }, $row;
}

如果我要构建像帖子中展示的数据结构,我认为唯一可能的方法就是复制%hash,对吗?如果我错了,请纠正我。 - chidori
不需要三个井号。你只需要两个就可以轻松完成,就像我在答案底部新添加的部分所示。 - ikegami
我已经更新了原始帖子并添加了额外的代码。请问我的方法是否正确,除了参考复制之外,是否有更好的方法? - chidori
基本上,尽可能晚地声明变量。具体而言,就像我之前提到的那样,在循环内部声明哈希变量。请参见附加回答。 - ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接