Как передать хэш функции в Perl? - PullRequest
38 голосов
/ 29 апреля 2009

У меня есть функция, которая принимает переменную и ассоциативный массив, но я не могу заставить их пройти правильно. Я думаю, что это как-то связано с объявлениями функций, однако я не могу понять, как они работают в Perl. Есть ли хорошая справка для этого и как мне выполнить то, что мне нужно?

Я должен добавить, что его нужно передать по ссылке.

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

Ответы [ 9 ]

67 голосов
/ 29 апреля 2009

Передайте ссылку вместо самого хеша. Как в

PrintAA("abc", \%fooHash);

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

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

См. Также perlfaq7: Как я могу передать / вернуть {Function, FileHandle, Array, Hash, Method, Regex}?

16 голосов
/ 29 апреля 2009

Этот код работает:

#!/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.
12 голосов
/ 29 апреля 2009

В качестве альтернативы:

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

В сущности, вы упускаете тот факт, что ассоциативный массив не является единственным аргументом (хотя ссылка на ассоциативный массив есть, как в ответе Пола Томблина).

4 голосов
/ 29 апреля 2009

Как обычно, есть несколько способов. Вот что 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 ...
}

Было бы более полезно, если бы вы могли показать какой-то код.

4 голосов
/ 29 апреля 2009

Похоже, вы должны передать ссылку на хеш.

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

PrintAA($foo, \%bar);

Причина, по которой вы не можете сделать

my %aa = shift;

потому что Perl объединяет все аргументы подпрограммы в один список, @_. Каждый элемент копируется, поэтому передача по ссылке также исключает эти копии.

3 голосов
/ 29 апреля 2009

Все вышеперечисленные методы работают, но я всегда предпочитал делать такие вещи:

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 голос
/ 11 февраля 2015

Все остальные ответы здесь пока кажутся мне довольно сложными. Когда я пишу Perl-функцию, я обычно «раскрываю» все переданные аргументы в первой строке функции.

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

Это похоже на другие языки, где вы объявляете функции как

... someFunction ( arg1, arg2, arg3 )

И если вы сделаете это таким образом и передадите хеш в качестве последнего аргумента, у вас все будет хорошо без каких-либо уловок или особой магии. E.g.:

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 «глотает» все остальные аргументы, всегда интерпретируя один как ключ, а следующий как значение (пока все элементы не будут использованы).

Вы не можете передать несколько хешей таким образом, и хеш не может быть первым аргументом, так как в противном случае он поглотит все и оставит все остальные аргументы неназначенными.

Конечно, точно так же работает для массива как последний аргумент. Единственная разница здесь в том, что массивы не различают ключи и значения, для них все оставшиеся аргументы являются значениями и просто помещаются в массив.

1 голос
/ 29 апреля 2009

Аргументы функций сведены в один массив (@_). Так что обычно проще передать хеши, чтобы они работали по ссылке.

Чтобы создать HASH:

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

Чтобы создать ссылку на этот HASH:

my $href = \%myhash

Для доступа к этому хешу по ссылке;

%$href

Итак, в вашем сабе:

my $myhref = shift;

keys %$myhref;
0 голосов
/ 29 апреля 2009

Используйте следующую подпрограмму для получения хеша или хешрефа - что бы ни прошло :)

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);

Ура! * * 1013

...