Путаница по поводу правильного использования разыменования в Perl - PullRequest
5 голосов
/ 19 июля 2011

На днях я заметил, что - изменяя значения в хэше - что когда вы разыменовываете хеш в Perl, вы фактически делаете копию этого хэша. Для подтверждения я написал этот быстрый маленький сценарий:

#! perl
use warnings;
use strict;

my %h = ();
my $hRef = \%h;
my %h2 = %{$hRef};
my $h2Ref = \%h2;

if($hRef eq $h2Ref) {
  print "\n\tThey're the same $hRef $h2Ref";
}
else {
  print "\n\tThey're NOT the same $hRef $h2Ref";
}
print "\n\n";

Выход:

    They're NOT the same HASH(0x10ff6848) HASH(0x10fede18)

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

Я осознаю тот факт, что я даже не заметил этого до сих пор, что, вероятно, не так уж важно (с точки зрения необходимости исправления во всех моих сценариях - но важно в будущем). Я думаю, что из-за этого можно заметить заметные различия в производительности, но это не меняет того факта, что я все еще в замешательстве.

Это задумано в perl? Есть ли какая-то явная причина, по которой я не знаю об этом; или это просто известно, и вы - как программист - должны знать и писать сценарии соответственно?

Ответы [ 4 ]

14 голосов
/ 19 июля 2011

Проблема в том, что вы создаете копию хэша для работы в этой строке:

my %h2 = %{$hRef};

И это понятно, так как многие посты здесь на SO используют эту идиому для создания локального именидля хэша, без объяснения того, что он на самом деле делает копию.

В Perl хеш - это множественное число, как массив.Это означает, что в контексте списка (например, при присвоении хешу) агрегат разбирается на список его содержимого.Этот список пар затем собирается в новый хэш, как показано на рисунке.

Что вы хотите сделать, это работать со ссылкой напрямую.

for (keys %$hRef) {...}
for (values %$href) {...}

my $x = $href->{some_key};
# or
my $x = $$href{some_key};

$$href{new_key} = 'new_value';

При работе с обычным хешемсимвол, который является либо %, когда речь идет о хэше, $, когда речь идет об одном элементе, и @, когда речь идет о срезе.За каждым из этих символов следует идентификатор.

 %hash          # whole hash
 $hash{key}     # element
 @hash{qw(a b)} # slice

Для работы со ссылкой с именем $href просто замените строку hash в приведенном выше коде на $href.Другими словами, $href - это полное имя идентификатора:

%$href          # whole hash
$$href{key}     # element
@$href{qw(a b)} # slice

Каждый из них может быть записан в более подробной форме как:

%{$href}
${$href}{key}
@{$href}{qw(a b)}

Что опять-такиподстановка строки '$href' вместо 'hash' в качестве имени идентификатора.

%{hash}
${hash}{key}
@{hash}{qw(a b)} 

Вы также можете использовать стрелку разыменования при работе с элементом:

$hash->{key}  # exactly the same as $$hash{key}

НоЯ предпочитаю синтаксис удвоенных сигил, так как он похож на весь синтаксис совокупности и фрагментов, а также на обычный не эталонный синтаксис.

Итак, подведем итог, каждый раз, когда вы пишете что-то вроде этого:

my @array = @$array_ref;
my %hash  = %$hash_ref;

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


Если вы хотите РЕАЛЬНОЕ локальное имя для хэша, но хотите работать с тем же хэшем,Вы можете использовать ключевое слово local для создания псевдонима.

 sub some_sub {
    my $hash_ref = shift;
    our %hash; # declare a lexical name for the global %{__PACKAGE__::hash}
    local *hash = \%$hash_ref;
        # install the hash ref into the glob
        # the `\%` bit ensures we have a hash ref

    # use %hash here, all changes will be made to $hash_ref

 }  # local unwinds here, restoring the global to its previous value if any

Это чистый Perl способ наложения псевдонимов.Если вы хотите использовать переменную my для хранения псевдонима, вы можете использовать модуль Data::Alias

7 голосов
/ 19 июля 2011

Вы путаете действия разыменования, которые по своей сути не создают копию, и используете хеш в контексте списка и назначаете этот список, что делает. $hashref->{'a'} является разыменованием, но, несомненно, влияет на исходный хеш. Это верно и для $#$arrayref или values(%$hashref).

Без назначения, просто контекст списка %$hashref - смешанный зверь; результирующий список содержит копии ключей хеша, но псевдонимы к фактическим значениям хеша. Вы можете увидеть это в действии:

$ perl -wle'$x={"a".."f"}; for (%$x) { $_=chr(ord($_)+10) }; print %$x'
epcnal

против

$ perl -wle'$x={"a".."f"}; %y=%$x; for (%y) { $_=chr(ord($_)+10) }; print %$x; print %y'
efcdab
epcnal

но %$hashref действует не иначе, чем %hash здесь.

5 голосов
/ 19 июля 2011

Нет, разыменование не создает копию референта. my создает новую переменную.

$ perl -E'
   my %h1; my $h1 = \%h1;
   my %h2; my $h2 = \%h2;
   say $h1;
   say $h2;
   say $h1 == $h2 ?1:0;
'
HASH(0x83b62e0)
HASH(0x83b6340)
0

$ perl -E'
   my %h;
   my $h1 = \%h;
   my $h2 = \%h;
   say $h1;
   say $h2;
   say $h1 == $h2 ?1:0;
'
HASH(0x9eae2d8)
HASH(0x9eae2d8)
1

Нет, $#{$someArrayHashRef} не создает новый массив.

0 голосов
/ 19 июля 2011

Если бы Perl сделал то, что вы предлагаете, переменные очень легко стали бы псевдонимами, что было бы намного более запутанным. На самом деле, вы можете создавать псевдонимы переменных с помощью globbing, но вам нужно делать это явно.

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