10

我一直确信,如果我给 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!'

这让我有点吃惊。这是如何运作的?子程序不是只获取参数的吗?它怎么知道它的地址?

4

3 回答 3

19

在 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 的任何别名构造时@_$_它们为您提供的 the 也是原始子例程参数的别名。如:

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;
}
于 2010-11-03T20:40:23.343 回答
11

这一切都在perldoc perlsub中详细记录。例如:

传入的任何参数都显示在数组@_ 中。因此,如果你调用一个带有两个参数的函数,它们将存储在 $_[0] 和 $_[1] 中。数组@_ 是一个本地数组,但它的元素是实际标量参数的别名。特别是,如果元素 $_[0] 被更新,则相应的参数也被更新(或者如果它不可更新,则会发生错误)。如果参数是调用函数时不存在的数组或散列元素,则该元素仅在(并且如果)它被修改或引用它时创建。(一些早期版本的 Perl 创建了该元素,无论该元素是否被分配。)分配给整个数组 @_ 删除该别名,并且不更新任何参数。

于 2010-11-03T20:39:54.547 回答
2

Perl 通过引用而不是值传递参数。见http://www.troubleshooters.com/codecorn/littperl/perlsub.htm

于 2010-11-03T20:33:04.677 回答