Perl子程序中的@_是如何工作的?

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!'

这让我有些惊讶。这是怎么做到的?难道子例程只会获取参数的吗?它怎么知道它的地址呢?


5
Perl不是C语言。 请不要期望它的行为像C语言,或者任何类似于C++或Java这样的派生自C语言的语言。 - Paul Tomblin
9
perldoc perlsub 是有关 Perl 子例程的官方文档。子例程是一段独立的代码块,可以在程序的不同位置被调用。该文档介绍了如何定义、调用和使用子例程,并提供了对参数传递、变量作用域和返回值的详细说明。此外,还讨论了一些高级主题,如闭包和匿名子例程。 - Ether
1
你的问题是关于@_而不是$_。在sub中,@_通常包含别名而不是值的副本。因此,如果您不希望在sub中出现这种行为,请确保在开始时将输入从@_复制到my变量中。 - aschepler
4
我相当确信这已经有非常详细的记录。 - tchrist
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不需要拷贝每个值。当然,缺点(或优点,取决于您的观点)是这些值保持别名关系,因此您无法改变它们而不改变原始值。

正如其他回答中在评论中提到的,当您在@_上使用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

详细文档都在perldoc perlsub中有所记录。例如:

传入的参数都会出现在数组 @_ 中。因此,如果您调用了带有两个参数的函数,这些参数将存储在 $_[0] 和 $_[1] 中。数组 @_ 是一个局部数组,但它的元素是实际标量参数的别名。特别是,如果更新了元素 $_[0],则相应的参数也会被更新(如果无法更新,则会出错)。如果参数是不存在于调用函数时的数组或哈希元素,则只有在修改它或取其引用时才会创建该元素。(一些早期版本的 Perl 会无论是否分配给元素都会创建元素)。对整个数组 @_ 进行赋值会删除别名,并且不会更新任何参数。


2
有人应该提到别名是可传递的,例如 for(@_) { s/^\s+//; s/\s+$// } - tchrist

2

1
+1,但如果OP改为编写my $notref = $_[0]; $notref = 'UNCHANGED!';,则引用会中断。这也是为什么大多数子例程都从将@_中的所有项目分配给命名变量开始的原因(并且具有可读性名称的附加好处)。 - Chris Lutz
4
您提供的链接文档自2003年以来就没有更新过,这比Perl的推荐版本5.8.8还要早。尽管大部分内容仍然准确无误,但在某些情况下可能会出现语法已经改变或改进的情况。 - Ether
2
请尝试最新的persub:http://perldoc.perl.org/perlsub.html,或者如果您想要旧版本:http://perldoc.perl.org/5.8.8/perlsub.html。 - daotoad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接