Как работает @_ в подпрограммах Perl? - PullRequest
10 голосов
/ 03 ноября 2010

Я всегда был уверен, что если я передам подпрограмму Perl простой скаляр, она никогда не сможет изменить свое значение вне подпрограммы. То есть:

my $x = 100;
foo($x);
# without knowing anything about foo(), I'm sure $x still == 100

Так что, если я хочу foo() изменить x, я должен передать ему ссылку на x.

Тогда я узнал, что это не тот случай:

sub foo {
 $_[0] = 'CHANGED!';
}
my $x = 100;
foo($x);
print $x, "\n"; # prints 'CHANGED!'

То же самое относится и к элементам массива:

my @arr = (1,2,3);
print $arr[0], "\n"; # prints '1'
foo($arr[0]);
print $arr[0], "\n"; # prints 'CHANGED!'

Это как бы удивило меня. Как это работает? Разве подпрограмма не получает только значение аргумента? Откуда он знает свой адрес?

Ответы [ 3 ]

19 голосов
/ 03 ноября 2010

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

так в этом подпункте:

sub example {
   # @_ is an alias to the arguments
   my ($x, $y, @rest) = @_;  # $x $y and @rest contain copies of the values
   my $args = \@_;  # $args contains a reference to @_ which maintains aliases
}

Обратите внимание, что этот псевдоним происходит после расширения списка, поэтому, если вы передали массив в example, массив расширяется в контексте списка, и @_ устанавливается на псевдонимы каждого элемента массива (но сам массив недоступно для example). Если вы хотите последнее, вы передадите ссылку на массив.

Псевдоним аргументов подпрограммы - очень полезная функция, но ее следует использовать с осторожностью. Чтобы предотвратить непреднамеренное изменение внешних переменных, в Perl 6 вы должны указать, что вы хотите записываемые аргументы с псевдонимами с помощью is rw.

Один из менее известных, но полезных приемов - использовать эту функцию псевдонимов для создания ссылок на массивы псевдонимов

my ($x, $y) = (1, 2);

my $alias = sub {\@_}->($x, $y);

$$alias[1]++;  # $y is now 3

или псевдонимы:

my $slice = sub {\@_}->(@somearray[3 .. 10]);  

также оказывается, что использование sub {\@_}->(LIST) для создания массива из списка на самом деле быстрее, чем
[ LIST ], поскольку Perl не нужно копировать каждое значение. Конечно, недостатком (или недостатком в зависимости от вашей перспективы) является то, что значения остаются псевдонимами, поэтому вы не можете изменить их без изменения оригиналов.

Как tchrist упоминает в комментарии к другому ответу, когда вы используете любую из псевдонимов Perl для @_, $_, которую они вам предоставляют, также является псевдонимом исходных аргументов подпрограммы. Такие как:

sub trim {s!^\s+!!, s!\s+$!! for @_}  # in place trimming of white space

Наконец, все это поведение является вложенным, поэтому при использовании @_ (или его части) в списке аргументов другой подпрограммы он также получает псевдонимы для аргументов первой подпрограммы:

sub add_1 {$_[0] += 1}

sub add_2 {
    add_1(@_) for 1 .. 2;
}
11 голосов
/ 03 ноября 2010

Все это подробно описано в perldoc perlsub . Например:

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

2 голосов
/ 03 ноября 2010

Perl передает аргументы по ссылке, а не по значению. Смотри http://www.troubleshooters.com/codecorn/littperl/perlsub.htm

...