Являются ли функции Perl вызовом по ссылке или по значению вызова? - PullRequest
28 голосов
/ 21 апреля 2011

Я пытаюсь выяснить подпрограммы Perl и как они работают.Начиная с perlsub я понимаю, что подпрограммы являются вызовом по ссылке и что назначение (например, my(@copy) = @_;) необходимо, чтобы превратить их в вызов по значению.

В следующемЯ вижу, что change вызывается по ссылке, потому что "a" и "b" заменены на "x" и "y".Но меня смущает, почему массив не расширяется дополнительным элементом "z"?

use strict;
use Data::Dumper;

my @a = ( "a" ,"b" );

change(@a);

print Dumper(\@a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
    @_[2] = "z";
}

Вывод:

$VAR1 = [
          'x',
          'y'
        ];

В дальнейшем вместо этого я передаю хешмассива.Почему ключ не меняется с «a» на «x»?

use strict;
use Data::Dumper;

my %a = ( "a" => "b" );

change(%a);

print Dumper(\%a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
}

Вывод:

$VAR1 = {
    'a' => 'y'
};

Я знаю, что real решение - передатьмассив или хэш по ссылке с использованием \@, но я бы хотел точно понять поведение этих программ.

Ответы [ 5 ]

38 голосов
/ 21 апреля 2011

Perl всегда проходит по ссылке.Просто иногда вызывающий абонент передает временные скаляры.

Первое, что вы должны понять, это то, что аргументы sub могут быть одним и единственным: список скаляров. * Нельзя передавать массивы или хэши вих.Массивы и хэши оцениваются, возвращая список их содержимого.Это означает, что

f(@a)

- это то же **, что и

f($a[0], $a[1], $a[2])

. Perl проходит по ссылке.В частности, Perl псевдоним каждого аргумента для элементов @_.Изменение элементов @_ изменит скаляры, возвращаемые $a[0] и т. Д., И, таким образом, изменит элементы @a.

Вторым важным моментом является то, что ключ массива или элемент хешаопределяет, где элемент хранится в структуре.В противном случае $a[4] и $h{k} потребуют просмотра каждого элемента массива или хэша, чтобы найти нужное значение.Это означает, что ключи не могут быть изменены.Перемещение значения требует создания нового элемента с новым ключом и удаления элемента со старым ключом.

Таким образом, всякий раз, когда вы получаете ключи массива или хэша, вы получаете copy из ключей.Свежие скаляры, так сказать.

Возвращаясь к вопросу,

f(%h)

- это то же самое **, что и

f(
   my $k1 = "a", $h{a},
   my $k2 = "b", $h{b}, 
   my $k2 = "c", $h{c}, 
)

@_ все еще связывается сзначения возвращаются %h, но некоторые из них являются просто временными скалярами, используемыми для хранения ключа.Их изменение не будет иметь длительного эффекта.

* - Некоторые встроенные модули (например, grep) больше похожи на операторы управления потоком (например, while).У них есть свои собственные правила синтаксического анализа, и, таким образом, они не ограничены обычной моделью подпрограммы.

** - Прототипы могут влиять на оценку списка аргументов, но это все равно приведет к списку скаляров.

9 голосов
/ 21 апреля 2011

Подпрограммы Perl принимают параметры в виде плоских списков скаляров. Массив, переданный в качестве параметра, для всех практических целей также является плоским списком. Даже хеш обрабатывается как плоский список из одного ключа, за которым следует одно значение, затем один ключ и т. Д.

Плоский список не передается в качестве ссылки, если вы не сделаете это явно. Тот факт, что изменение $_[0] изменяет $a[0], заключается в том, что элементы @_ становятся псевдонимами для элементов, передаваемых в качестве параметров. Изменение $_[0] аналогично изменению $a[0] в вашем примере. Но хотя это примерно аналогично общему понятию «передача по ссылке», поскольку оно применимо к любому языку программирования, оно не передает конкретно ссылку на Perl; Ссылки Perl отличаются (и действительно, «ссылка» - перегруженный термин). Псевдоним (в Perl) является синонимом чего-то, где в качестве ссылки он похож на указатель на что-то.

Как утверждает perlsyn, если вы присваиваете @_ в целом, вы нарушаете его псевдоним. Также обратите внимание, что если вы попытаетесь изменить $_[0], а $_[0] окажется литералом, а не переменной, вы получите ошибку. С другой стороны, изменение $_[0] изменяет значение вызывающей стороны, если оно является изменяемым. Таким образом, в первом примере изменение $_[0] и $_[1] распространяется на @a, поскольку каждый элемент @_ является псевдонимом для каждого элемента в @a.

Ваш второй пример немного хитрый. Хэш-ключи неизменны. Perl не предоставляет способ изменить ключ хеша, кроме его удаления. Это означает, что $_[0] не может быть изменено. Когда вы пытаетесь изменить $_[0] Perl не может выполнить этот запрос. Вероятно, следует бросить предупреждение, но это не так. Видите ли, плоский список, переданный ему, состоит из unmodifiable-key, за которым следует modifiable-value и т. Д. В основном это не проблема. Я не могу думать ни о какой причине, чтобы изменить отдельные элементы хэша так, как вы демонстрируете; поскольку хэши не имеют определенного порядка, у вас не будет простого контроля над тем, какие элементы в @_ распространяются обратно на какие значения в %a.

Как вы указали, правильный протокол должен передавать \@a или \%a, чтобы их можно было назвать $_[0]->{element} или $_[0]->[0]. Несмотря на то, что обозначения немного сложнее, через некоторое время они становятся второй натурой, и, на мой взгляд, гораздо понятнее относительно того, что происходит.

Обязательно посмотрите документацию perlsub . В частности:

Все передаваемые аргументы отображаются в массиве @_. Следовательно, если вы вызываете функцию с двумя аргументами, они будут сохранены в $_[0] и $_[1]. Массив @_ является локальным массивом, но его элементы являются псевдонимами для фактических скалярных параметров. В частности, если элемент $_[0] обновляется, соответствующий аргумент обновляется (или возникает ошибка, если он не обновляется). Если аргумент является элементом массива или хеша, который не существовал при вызове функции, этот элемент создается только тогда, когда (и если) он был изменен или была взята ссылка на него. (Некоторые более ранние версии Perl создавали элемент независимо от того, был ли ему присвоен элемент.) Присвоение всего массива @_ удаляет этот псевдоним и не обновляет никаких аргументов.

4 голосов
/ 21 апреля 2011

(Обратите внимание, что use warnings даже важнее use strict.)

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

Таким образом, сдвиги, сращивания, добавление дополнительных элементов и т. д. в @_ не влияют на все переданные данные, хотяони могут изменить индекс или удалить из массива один из исходных псевдонимов.

Поэтому, когда вы вызываете change(@a), это помещает в стек два псевдонима, один для $a[0] и один для $a[1],change(%a) сложнее;%a превращается в чередующийся список ключей и значений, где значения являются фактическими значениями хеш-функции, а их изменение изменяет то, что хранится в хэше, но где ключи являются просто копиями, больше не связанными с хеш-кодом.

2 голосов
/ 21 апреля 2011

Perl не передает массив или сам хэш по ссылке, он разворачивает записи (элементы массива или ключи и значения хеша) в список и передает этот список функции.Затем @_ позволяет вам получить доступ к скалярам в качестве ссылок.

Это примерно то же самое, что и запись:

@a = (1, 2, 3);

$b = \$a[2];

${$b} = 4;

@a now [1, 2, 4];

Вы заметите, что в первом случае вы не смогли добавитьдополнительный элемент для @a, все, что произошло, это то, что вы изменили уже существующие элементы @a.Во втором случае ключи хеша на самом деле не существуют в хэше как скаляры, поэтому их нужно создавать как копии во временных скалярах, когда создается расширенный список хеша для передачи в функцию.Изменение этого временного скаляра не приведет к изменению ключа хеша, поскольку он не является ключом хеша.

Если вы хотите изменить массив или хеш в функции, вам нужно будет передать ссылку на контейнер:

change(\%foo);

sub change {
   $_[0]->{a} = 1;
}
0 голосов
/ 21 апреля 2011

Во-первых, вы путаете символ @ с указанием массива.Это на самом деле список.Когда вы вызываете Change (@a), вы передаете список функции, а не объекту массива.

Случай с хешем немного отличается.Perl оценивает ваш вызов в список и вместо этого передает значения в виде списка.

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