【问题标题】:How does @_ work in Perl subroutines?@_ 在 Perl 子例程中如何工作?
【发布时间】:2010-11-03 20:25:54
【问题描述】:

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

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

【问题讨论】:

  • Perl 不是 C。不要期望它表现得像 C 或任何 C 派生语言,如 C++ 或 Java。
  • 您的问题是关于@_,而不是$_。而@_ 中的sub 通常包含别名而不是值的副本。因此,如果您不希望在 sub 中出现这种行为,请务必在开始时将输入从 @_ 复制到 my 变量。
  • 相当确定这是有据可查的。

标签: perl pass-by-reference parameter-passing subroutine


【解决方案1】:

在 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;
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 有人应该提到别名是可传递的,例如for(@_) { s/^\s+//; s/\s+$// }
    【解决方案3】:

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

    【讨论】:

    • +1,但如果 OP 改为写入 my $notref = $_[0]; $notref = 'UNCHANGED!';,则引用中断@ 到命名变量。
    • 您链接到的文档自 2003 年以来一直没有更新,该版本早于 5.8.8,通常被认为是最早的“推荐允许”版本的 Perl。其中大部分仍然是准确的,但在某些情况下语法已经改变或改进。
    • perldoc.perl.org/perlsub.html 尝试最新的 persub,或者您想要旧版本:perldoc.perl.org/5.8.8/perlsub.html
    猜你喜欢
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 2010-11-12
    • 1970-01-01
    相关资源
    最近更新 更多