【问题标题】:How do I pass a hash to a function in Perl?如何将哈希传递给 Perl 中的函数?
【发布时间】:2023-03-28 13:30:03
【问题描述】:

我有一个函数,它接受一个变量和一个关联数组,但我似乎无法让它们正确传递。我认为这与函数声明有关,但是我无法弄清楚它们在 Perl 中是如何工作的。对此有很好的参考吗?我该如何完成我需要的工作?

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

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

【问题讨论】:

  • 你能告诉我们调用代码吗?

标签: perl function parameter-passing declaration


【解决方案1】:

传递引用而不是散列本身。如

PrintAA("abc", \%fooHash);

sub PrintAA
{
  my $test = shift;
  my $aaRef = shift;

  print $test, "\n";
  foreach (keys %{$aaRef})
  {
    print $_, " : ", $aaRef->{$_}, "\n";
  }
}

另见 perlfaq7:How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}?

【讨论】:

  • 虽然不是我想的那样,但这是最简单的方法。
【解决方案2】:

此代码有效:

#!/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, ... };

不同之处在于 { ... } 表示法生成一个哈希引用,而 ( ... ) 表示法生成一个列表,该列表映射到一个哈希(与哈希引用相反)。同样, [ ... ] 生成数组 ref 而不是数组。

确实,将“主”代码更改为: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.

【讨论】:

  • TMTOWTDI,但我更喜欢这种方法。
  • 数组上下文业务到底是做什么的?
  • 其实我不是在问 my($test, %aa) = @_;我在问 my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA );与我的 %hash = { 'aaa' => 1, ... };
  • 请记住,如果数组很大,引用效率更高。这种调用风格将所有数组元素压入堆栈,并将它们全部弹出。引用是单个元素,与数组大小无关。
【解决方案3】:

或者:

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 将子例程的所有参数扁平化为一个列表,@_。每个元素都被复制,因此通过引用传递也可以避免这些副本。

    【讨论】:

      【解决方案5】:

      像往常一样,有几种方法。以下是最受推崇的样式指针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 ...
      }
      

      如果你能显示一些代码会更有用。

      【讨论】:

        【解决方案6】:

        以前答案中的所有方法都有效,但这始终是我喜欢做这样的事情的方式:

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

        注意:我还稍微更改了您的代码。 Perl 的双引号字符串会将"$test" 解释为$test 的值,而不是实际的字符串'$test',因此您不需要那么多.s。

        另外,我对原型的工作方式有误。要传递哈希,请使用:

        PrintAA("test", %hash);
        

        要打印哈希引用,请使用:

        PrintAA("test", %$ref_to_hash);
        

        当然,现在您无法修改 $ref_to_hash 引用的哈希,因为您发送的是副本,但您可以修改原始的 %hash,因为您将其作为引用传递。

        【讨论】:

        • 投反对票,因为 1) 这段代码不起作用,2) Perl 原型并没有像大多数人(显然包括你)所期望的那样。它们非常适合模拟内置函数的行为,但没有太多用处。原型的“\%”部分表示第二个参数必须是一个散列并通过引用传递它。它必须是一个 real 散列。它不能是 hashref 或 key => value 对的列表。
        • 感谢您的更正。 \% 不能作为参考是没有意义的,但这是真的。我认为他不会传递 key => value 对的列表,因为他的函数似乎正在修改他作为参数传入的哈希,但其他点是有效的。
        【解决方案7】:

        函数的参数被扁平化为单个数组 (@_)。因此,通过引用将哈希值传递给函数通常是最简单的。

        创建一个哈希

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

        创建对该散列的引用:

        my $href = \%myhash
        

        通过引用访问该哈希;

        %$href
        

        所以在你的潜艇中:

        my $myhref = shift;
        
        keys %$myhref;
        

        【讨论】:

          【解决方案8】:

          到目前为止,这里的所有其他回复对我来说似乎都相当复杂。当我编写 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“吞没”所有其他参数,总是将一个解释为键,下一个解释为值(直到所有元素都用完)。

          您不能以这种方式传递多个散列,并且散列不能作为第一个参数,否则它会吞没所有内容并使所有其他参数未分配。

          当然,数组作为最后一个参数的工作方式完全相同。这里唯一的区别是数组不区分键和值。对他们来说,剩下的所有参数都是值,只是被推送到数组中。

          【讨论】:

            【解决方案9】:

            使用下面的 sub 来获取 hash 或 hashref - 无论传递什么 :)

            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 - 当子例程作为方法调用时,原型没有任何作用,它们很糟糕,令人困惑并且会破坏事物。正如其他人之前发布的那样,除非您需要像内置一样制作子作品,否则不要使用它们。
            猜你喜欢
            • 2013-09-27
            • 2011-09-06
            • 2011-02-01
            • 2012-10-07
            • 1970-01-01
            • 1970-01-01
            • 2017-02-19
            • 2017-07-22
            • 1970-01-01
            相关资源
            最近更新 更多