通过值传递vs传递Perl哈希的引用

时间:2013-03-09 22:03:59

标签: perl hash pass-by-reference pass-by-value

我正在使用子程序制作一些不同的哈希映射。我目前正在通过引用传递hashmap,但是多次执行时会出现这种情况。我应该按值传递散列值还是传递散列引用?

use strict;
use warnings;

sub fromFile($){
    local $/;
    local our %counts =();
     my $string = <$_[0]>;
    open FILE, $string or die $!;
    my $contents = <FILE>;
    close FILE or die $!;

    my $pa = qr{
        ( \pL {2} )
        (?{
            if(exists $counts{lc($^N)}){
                $counts{lc($^N)} = $counts{lc($^N)} + 1;
            }
            else{
                $counts{lc($^N)} = '1';
            }
        })
        (*FAIL)
    }x;

     $contents =~ $pa;

    return %counts;

}

sub main(){
    my %english_map = &fromFile("english.txt");
    #my %german_map = &fromFile("german.txt");
}

main();

当我单独运行不同的txt文件时,我没有遇到任何问题,但两者都有一些冲突。

3 个答案:

答案 0 :(得分:11)

三条评论:

不要混淆传递引用与传递引用

传递引用正在传递包含引用(一种值)的标量。

编译器在传递参数时通过引用传递参数而不进行复制。

编译器在传递参数副本时,按值传递参数

参数总是通过Perl

中的引用传递

修改函数的参数(@_的元素)将改变调用者中的相应变量。这是复制参数的惯例存在的原因之一。

my ($x, $y) = @_;   # This copies the args.

当然,复制参数的主要原因是“命名”它们,但它使我们免于直接使用@_元素所带来的一些令人讨厌的惊喜。

$ perl -E'sub f { my ($x) = @_; "b"=~/(.)/; say $x;    } "a"=~/(.)/; f($1)'
a

$ perl -E'sub f {               "b"=~/(.)/; say $_[0]; } "a"=~/(.)/; f($1)'
b

无法在Perl

中传递数组或散列作为参数

唯一可以传递给Perl sub的是标量列表。 (这也是唯一一个可以归还的东西。)

由于@a在列表上下文中评估为$a[0], $a[1], ...

foo(@a)

相同
foo($a[0], $a[1], ...)

这就是为什么我们创建对我们想要传递给sub的数组或哈希的引用并传递引用。

如果我们不这样做,数组或散列将被计算为标量列表,并且必须在sub中重建。不仅如此昂贵,在

等情况下也是不可能的
foo(@a, @b)

因为foo无法知道@a返回了多少参数以及@b返回了多少参数。

请注意,可以使用原型将其看作数组或散列作为参数传递,但原型只会导致对数组/散列的引用自动创建,而这实际上是传递给子的

答案 1 :(得分:9)

由于某些原因,您应该使用pass-by-reference,但您显示的代码会按值返回哈希。

  • 您应该使用my而不是local,除了$/之类的内置变量,然后才能使用尽可能小的范围。

    < / LI>
  • 子程序的原型几乎不是一个好主意。他们做了一些非常具体的事情,如果你不知道那是什么,就不应该使用它们。

  • 使用&符号(如&fromFile("english.txt"))调用子程序,自大约二十年前的Perl 4以来就不正确。它以至少两种不同的方式影响传递给子程序的参数,这是一个坏主意。

  • 我不确定为什么要使用my $string = <$_[0]>的文件glob。您是否期望在作为参数传递的文件名中使用通配符?如果是这样,那么你将打开并只读取第一个匹配的文件,否则不需要glob。

  • $fh这样的词法文件句柄比FILE之类的裸字文件句柄更好,并且在它们被销毁时会隐式关闭 - 通常在声明它们的块的末尾。

  • 我不确定您的哈希%counts是如何填充的。没有正则表达式可以填充哈希值,但我必须相信你!

试试这个版本。熟悉Perl的人会感谢你(具有讽刺意味的是!)没有使用驼峰式变量名。并且很少看到声明和调用的main子例程。那是C,这是Perl。

更新我已将此代码更改为您的原始正则表达式。

use strict;
use warnings;

sub from_file {

    my ($filename) = @_;

    my $contents = do {
        open my $fh, '<', $filename or die qq{Unable to open "$filename": $!};
        local $/;
        my $contents = <$fh>;
    };

    my %counts;
    $counts{lc $1}++ while $contents =~ /(?=(\pL{2}))/g;

    return \%counts;
}

sub main {
    my $english_map = from_file('english.txt');
    my $german_map  = from_file('german.txt');
}

main();

答案 2 :(得分:4)

您可以使用引用或传递整个哈希或数组。你的选择。有两个问题可能会让您选择一个而不是另一个:

  1. 传递其他参数
  2. 内存管理
  3. Perl实际上没有子程序参数。相反,你只是传递一个参数数组。如果你是子程序,那么看看哪个数组有更多的元素。我不能这样做:

    foo(@first, @second);
    

    因为我要传递的是一个结合了两者所有成员的大数组。哈希也是如此。想象一个程序需要两个哈希并找到具有公共密钥的程序:

    @common_keys = common(%hash1, %hash1);
    

    同样,我将两个哈希中的所有键及其值组合成一个大数组。

    解决此问题的唯一方法是传递参考:

    foo(\@first, \@second);
    @common_keys = common(\%hash1, \%hash2);
    

    在这种情况下,我正在传递这两个哈希值存储在内存中的内存位置。我的子例程可以使用这些哈希引用。但是,你必须要小心,我将用第二种解释来解释。

    传递引用的第二个原因是内存管理。如果我的数组或哈希是几十个条目,那真的并不重要。但是,假设我的哈希或数组中有10,000,000个条目。复制所有这些成员可能需要相当多的时间。通过引用传递可以节省我的记忆,但成本很高。大多数时候,我使用子程序作为不影响我的主程序的方式。这就是为什么子程序被假设使用它们自己的变量以及为什么你在大多数关于变量范围的编程课程中被教授的原因。

    然而,当我传递一个引用时,我打破了这个范围。这是一个没有传递参考的简单程序。

    #! /usr/bin/env perl
    use strict;
    use warnings;
    
    my @array = qw(this that the other);
    
    foo (@array);
    
    print join ( ":", @array ) . "\n";
    
    sub foo {
        my @foo_array = @_;
        $foo_array[1] = "FOO";
    }
    

    请注意,子例程foo 1 正在更改传入的数组的第二个元素。但是,即使我将@array传入foo,子例程也不会更改@array的值。那是因为子程序正在处理副本(由my @foo_array = @_;创建)。一旦子程序存在,副本就会消失。

    当我执行这个程序时,我得到:

    this:that:the:other
    

    现在,这是相同的程序,除了我传入一个引用,为了内存管理,我使用了这个引用:

    #! /usr/bin/env perl
    use strict;
    use warnings;
    
    my @array = qw(this that the other);
    
    foo (\@array);
    
    print join ( ":", @array ) . "\n";
    
    sub foo {
        my $foo_array_ref = shift;
        $foo_array_ref->[1] = "FOO";
    }
    

    当我执行这个程序时,我得到:

    this:FOO:the:other
    

    那是因为我没有传入数组,而是传入该数组。它与@array的内存位置相同。因此,更改子例程中的引用会导致在我的主程序中更改它。大多数时候,你不想这样做。

    您可以通过传入引用然后将该引用复制到数组来解决此问题。例如,如果我这样做了:

    sub foo {
        my @foo_array = @{ shift() };
    

    我将复制我对另一个数组的引用。它保护我的变量,但它确实意味着我将我的数组复制到另一个需要时间和内存的对象上。早在20世纪80年代,当我第一次编程时,这是一个大问题。但是,在这个千兆字节内存和四核处理器时代,主要问题不是内存管理,而是可维护性。即使您的数组或哈希包含1000万个条目,您也可能不会注意到任何时间或内存问题。

    这也是另一种方式。我可以从我的子例程返回对哈希或整个哈希的引用。许多人喜欢返回参考,但这可能会有问题。

    在面向对象的Perl编程中,我使用引用来跟踪我的对象。通常,我会引用一个可以用来存储其他值,数组和哈希的哈希。

    在最近的一个程序中,我在计算ID以及在日志文件中引用它们的次数。这存储在一个对象中(它只是对哈希的引用)。我有一个方法可以返回ID的整个散列及其计数。我本可以做到这一点:

    return $self->{COUNT_HASH};
    

    但是,如果用户开始修改我通过的引用,会发生什么?他们实际上是在操纵我的对象而不使用我的方法来添加和减去ID。不是我希望他们做的事情。相反,我创建一个新的哈希,然后返回对该哈希的引用:

    my %hash_counts = % { $self-{COUNT_HASH} };
    return \%hash_count;
    

    这复制了我对数组的引用,然后我将引用传递给了数组。这可以保护我的数据免受外部操纵。我仍然可以返回一个引用,但是如果不通过我的方法,用户将无法再访问我的对象。

    顺便说一下,我喜欢使用wantarray,让调用者可以选择他们想要的数据:

    my %hash_counts = %{ $self->{COUNT_HASH} };
    return want array ? %hash_counts : \%hash_counts;
    

    这允许我根据用户调用我的对象的方式返回引用或哈希:

    my %hash_counts = $object->totals();      # Returns a hash
    my $hash_counts_ref = $object->totals();  # Returns a reference to a hash
    

    1 脚注:@_数组指向与调用子例程的参数相同的内存位置。因此,如果我传入foo(@array)然后执行$_[1] = "foo";,我将更改@array的第二个元素。