如何在Perl中将哈希传递到函数?

39

我有一个函数,它需要一个变量和一个关联数组作为参数,但是我似乎无法正确地传递它们。我认为这与函数声明有关,但是我不知道在Perl中如何工作。是否有好的参考资料来解决这个问题?

我应该补充一点,它需要通过引用传递。

sub PrintAA
{
    my $test = shift;
    my %aa   = shift;
    print $test . "\n";
    foreach (keys %aa)
    {
        print $_ . " : " . $aa{$_} . "\n";
        $aa{$_} = $aa{$_} . "+";
    }
}

你可以展示一下调用代码吗? - Jonathan Leffler
9个回答

68

虽然不是我认为应该采用的方式,但这是最简单的方法。 - rlbond

17

这段代码有效:

#!/bin/perl -w

use strict;

sub PrintAA
{
    my($test, %aa) = @_;
    print $test . "\n";
    foreach (keys %aa)
    {
        print $_ . " : " . $aa{$_} . "\n";
    }
}

my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA );

PrintAA("test", %hash);

重点在于函数中 my() '语句' 中对于数组上下文的运用。

数组上下文到底是做什么的?

简单来说,它使其正常工作。

这意味着将参数数组@_的第一个值分配给$test,并将剩余的项分配给哈希表%aa。鉴于我调用它的方式,在@_中有奇数个项目,因此一旦第一个项目被分配给$test,就有偶数个项目可用于分配给%aa,每对的第一个项目是键(在我的示例中为'aaa'、'bbb'、'ccc'),第二个是相应的值。

可以用@aa替换%aa,在这种情况下,数组将有6个项目。也可以用$aa替换%aa,在这种情况下,变量$aa将包含值'aaa',而@_中的其余值将被赋值忽略。

如果省略变量列表周围的括号,Perl将拒绝编译代码。其中一种替代答案显示了以下符号:

my $test = shift;
my(%aa) = @_;

这基本上与我写的内容相同;不同之处在于,在两个my语句之后,@_在这个变化版本中只包含6个元素,而在单个my版本中,它仍然包含7个元素。 SO上肯定还有其他关于数组环境的问题。
实际上,我不是在询问my($test, %aa) = @_;,我在询问my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA );my %hash = { 'aaa' => 1, ... };之间的区别。

区别在于{...}符号生成哈希引用,而(…)符号生成列表,它映射到哈希(而不是哈希引用)。同样,[...]生成数组引用而不是数组。

确实,将'main'代码更改为:my(%hash) = {...};,你会得到一个运行时错误(但不是编译时错误) - 请注意行号,因为我已经向我的文件中添加了替代编码:

Reference found where even-sized list expected at xx.pl line 18.
...
Use of uninitialized value in concatenation (.) or string at xx.pl line 13.

2
有许多种方法去做,但我更喜欢这种方式。 - spoulson
数组上下文业务实际上是做什么的? - Paul Tomblin
实际上,我不是在问 my($test, %aa) = @_; 而是在问 my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => &PrintAA ); 与 my %hash = { 'aaa' => 1, ... }; 之间的区别。 - Paul Tomblin
3
请记住,如果数组很大,引用更加高效。这种调用方式将所有数组元素推送到堆栈上,然后将它们全部弹出。相比之下,一个引用只是一个单独的元素,不管数组大小如何。 - Craig Lewis

12

或者:

sub PrintAA
{
    my $test       = shift;
    my %aa         = @_;
        print $test . "\n";
        foreach (keys %aa)
        {
                print $_ . " : " . $aa{$_} . "\n";
                $aa{$_} = $aa{$_} . "+";
        }
}
你基本上忽略了一个要点,那就是关联数组并不是单个参数(虽然关联数组引用可以作为单个参数,比如Paul Tomblin的回答中所示)。

4

看起来你应该传递一个哈希表的引用。

sub PrintAA
{
   my $test = shift;
   my $aa = shift;
   if (ref($aa) != "HASH") { die "bad arg!" }
   ....
}

PrintAA($foo, \%bar);

你无法完成一个

标签的原因是

my %aa = shift;

这是因为Perl会将所有传递给子程序的参数压缩成一个列表@_。每个元素都会被复制,因此通过引用传递可以避免这些复制。


4
通常有几种方法。以下是最受尊敬的样式指针之一Perl Best Practices对于将参数传递给函数的建议:
对于具有三个以上参数的任何子例程,请使用命名参数的哈希表。
但是,由于您只有两个参数,因此可以像这样直接传递它们:
my $scalar = 5;
my %hash = (a => 1, b => 2, c => 3);

func($scalar, %hash)

函数的定义如下:

sub func {
    my $scalar_var = shift;
    my %hash_var = @_;

    ... Do something ...
}

如果您能展示一些代码,那将更加有用。

3

之前回答中提到的所有方法都可以使用,但我更喜欢像这样做事情:

sub PrintAA ($\%)
{
    my $test       = shift;
    my %aa         = ${shift()};
    print "$test\n";
    foreach (keys %aa)
    {
        print "$_ : $aa{$_}\n";
        $aa{$_} = "$aa{$_}+";
    }
}

注意:我还稍微修改了你的代码。Perl的双引号字符串会将"$test"解释为$test的值,而不是实际的字符串'$test',所以你不需要那么多个.
此外,我之前对原型的理解是错误的。要传递哈希,请使用以下代码:
PrintAA("test", %hash);

要打印哈希引用,请使用以下代码:

PrintAA("test", %$ref_to_hash);

当然,现在你不能修改$ref_to_hash所引用的哈希值,因为你正在发送副本,但你可以修改原始的%hash,因为你将其作为引用传递。


因为1)这段代码不起作用,2)Perl原型不像大多数人(显然包括你在内)期望的那样工作,所以被downvote了。它们适用于模拟内置函数的行为,但除此之外没有太多用处。原型中的“\%”部分表示第二个参数必须是哈希,并通过引用传递。它必须是一个真正的哈希。它不能是哈希引用或键值对列表。 - Michael Carman
感谢您的更正。虽然很难理解“%”不能作为引用,但这是事实。我认为他不会传递键值对列表,因为他的函数似乎是修改他作为参数传入的哈希表,但其他观点是正确的。 - Chris Lutz

2

函数的参数会被压缩成一个单一的数组 (@_)。因此,通常最简单的方法是通过引用传递哈希到函数中。

创建一个 哈希

my %myhash = ( key1 => "val1", key2 => "val2" );

为创建对该哈希的引用,请执行以下操作:
my $href = \%myhash

要通过引用访问该哈希表;

%$href

所以在您的子程序中:
my $myhref = shift;

keys %$myhref;

1

到目前为止,这里的其他回复似乎对我来说都很复杂。当我编写Perl函数时,通常会在函数的第一行“展开”所有传递的参数。

sub someFunction {
    my ( $arg1, $arg2, $arg3 ) = @_;

这与其他编程语言类似,您可以将函数声明为:
... someFunction ( arg1, arg2, arg3 )

如果您按照这种方式并将哈希作为最后一个参数传递,您就不需要任何技巧或特殊魔法。例如:

sub testFunc {
    my ( $string, %hash ) = @_;
    print "$string $hash{'abc'} $hash{'efg'} $string\n";
}

my %testHash = (
    'abc' => "Hello,",
    'efg' => "World!"
);
testFunc('!!!', %testHash);

输出结果如预期:

!!! Hello, World! !!!

这是因为在Perl中,参数总是作为标量值的数组传递,如果传递哈希表,则其键值对将添加到该数组中。在上面的示例中,传递给函数的参数作为数组(@_)实际上是:
'!!!', 'abc', 'Hello,', 'efg', 'World!'

而'!!!'只是被分配给%string,而%hash则“吞噬”所有其他参数,始终将一个参数解释为键,下一个参数解释为值(直到使用完所有元素)。

您无法以此方式传递多个哈希,并且哈希不能是第一个参数,否则它将吞噬所有其他参数并使其未分配。

当然,对于作为最后一个参数的数组,完全相同的方法也适用。唯一的区别在于数组不区分键和值。对于它们来说,所有剩余的参数都是值,并且只需将它们推送到数组中即可。


0
使用以下子程序获取哈希或哈希引用-无论传递的是什么 :)
sub get_args { ref( $_[0] ) ? shift() : ( @_ % 2 ) ? {} : {@_}; }
sub PrintAA
{
  my $test = shift;
  my $aa = get_args(@_);;

  # Then
  $aa->{somearg} # Do something
  $aa->{anotherearg} # Do something

}

像这样调用您的函数:

printAA($firstarg,somearg=>1, anotherarg=>2)

或者像这样(无所谓):

printAA($firstarg, {somearg=>1, anotherarg=>2})

或者像这样(无所谓):

my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \PrintAA );

PrintAA("test", %hash);

使用原型可以让您做同样的事情,同时还可以对子例程的参数进行编译时检查。此外,您的get_args将被引用数组所欺骗。 - Chris Lutz
@Chris - 当一个子程序作为方法被调用时,原型没有任何作用,它们很糟糕、令人困惑,并且会破坏事物。正如其他人之前发布的帖子所述,除非需要使子程序像内置函数一样工作,否则不要使用它们。 - converter42

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