6

我正在使用一个子程序来制作一些不同的哈希映射。我目前正在通过引用传递哈希图,但是在多次这样做时会发生冲突。我应该按值传递哈希还是传递哈希引用?

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 文件时,我没有遇到任何问题,但是两者都有一些冲突。

4

3 回答 3

14

三个评论:

不要混淆传递引用和通过引用传递

传递引用是传递一个包含引用(一种值)的标量。

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

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

在 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 子程序的是标量列表。(这也是唯一可以被一个人退回的东西。)

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

foo(@a)

是相同的

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

这就是为什么我们创建对我们想要传递给子的数组或散列的引用并传递引用的原因。

如果我们不这样做,则数组或散列将被评估为标量列表,并且必须在 sub 内部重建。不仅如此昂贵,而且在诸如此类的情况下是不可能的

foo(@a, @b)

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

请注意,可以使用原型使其看起来像一个数组或哈希作为参数传递,但原型只是导致自动创建对数组/哈希的引用,这就是实际传递给子的内容。

于 2013-03-10T02:29:54.470 回答
9

For a couple of reasons you should use pass-by-reference, but the code you show returns the hash by value.

  • You should use my rather than local except for built-in variables like $/, and then for only as small a scope as possible.

  • Prototypes on subroutines are almost never a good idea. They do something very specific, and if you don't know what that is you shouldn't use them.

  • Calling subroutines using the ampersand sigil, as in &fromFile("english.txt"), hasn't been correct since Perl 4, about twenty years ago. It affects the parameters delivered to a subroutine in at least two different ways and is a bad idea.

  • I'm not sure why you are using a file glob with my $string = <$_[0]>. Are you expecting wildcards in the filename passed as the parameter? If so then you will be opening and reading only the first matching file, otherwise the glob is unnecessary.

  • Lexical file handles like $fh are better than bareword file handles like FILE, and will be closed implicitly when they are destroyed - usually at the end of the block where they are declared.

  • I am not sure how your hash %counts gets populated. No regex on its own can fill a hash, but I will have to trust you!

Try this version. People familiar with Perl will thank you (ironically!) for not using camel-case variable names. And it is rare to see a main subroutine declared and called. That is C, this is Perl.

Update I have changed this code to do what your original regex did.

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();
于 2013-03-09T23:06:44.177 回答
4

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

  1. 传递其他参数
  2. 内存管理

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";
}

请注意,子例程foo1正在更改传入数组的第二个元素。但是,即使我传入@arrayfoo子例程也不会更改 的值@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() };

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

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

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

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

return $self->{COUNT_HASH};

但是,如果用户开始修改我传递的那个引用,会发生什么?他们实际上会在不使用我的方法来添加和减去 ID 的情况下操作我的对象。不是我想让他们做的事情。相反,我创建了一个新的散列,然后返回对该散列的引用:

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

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

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

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.

于 2013-03-10T01:37:54.563 回答