Perl子例程是按引用调用还是按值调用?

Perl子例程是按引用调用还是按值调用?,perl,Perl,我试图弄清楚Perl子例程以及它们是如何工作的。 根据我的理解,子程序是通过引用调用的,需要赋值(如my(@copy)=@;)才能将它们转换为按值调用 在下面,我看到change通过引用调用,因为“a”和“b”被更改为“x”和“y”。但是我不明白为什么数组没有扩展一个额外的元素“z” 输出: $VAR1 = [ 'x', 'y' ]; $VAR1 = { 'a' => 'y' }; 在下面,我传递一个散列而不是数组。为什么

我试图弄清楚Perl子例程以及它们是如何工作的。 根据我的理解,子程序是通过引用调用的,需要赋值(如
my(@copy)=@;
)才能将它们转换为按值调用

在下面,我看到
change
通过引用调用,因为“a”和“b”被更改为“x”和“y”。但是我不明白为什么数组没有扩展一个额外的元素“z”

输出:

$VAR1 = [
          'x',
          'y'
        ];
$VAR1 = {
    'a' => 'y'
};
在下面,我传递一个散列而不是数组。为什么钥匙没有从“a”改为“x”

输出:

$VAR1 = [
          'x',
          'y'
        ];
$VAR1 = {
    'a' => 'y'
};

我知道真正的解决方案是使用
\@
通过引用传递数组或散列,但我想准确了解这些程序的行为。

Perl不通过引用传递数组或散列本身,而是展开条目(数组元素或散列键和值)并将此列表传递给函数。@@u然后允许您作为引用访问标量

这与写作大致相同:

@a = (1, 2, 3);

$b = \$a[2];

${$b} = 4;

@a now [1, 2, 4];
您会注意到,在第一种情况下,您无法向@a添加额外的项,所发生的只是您修改了已经存在的@a的成员。在第二种情况下,散列键实际上不作为标量存在于散列中,因此当创建散列的扩展列表以传递到函数中时,需要在临时标量中创建散列键作为副本。修改此临时标量不会修改哈希键,因为它不是哈希键

如果要修改函数中的数组或散列,则需要传递对容器的引用:

change(\%foo);

sub change {
   $_[0]->{a} = 1;
}
(请注意,
使用警告
使用严格的
更重要)

@
本身不是对任何东西的引用,它是一个数组(实际上,只是堆栈的一个视图,但如果您执行类似于引用它的操作,它会变形为一个真实数组),其元素都是传递参数的别名。传递的参数是传递的单个标量;没有传递数组或散列的概念(尽管可以传递对数组或散列的引用)

因此,移位、拼接、添加到
@
的附加元素等不会影响传递的任何内容,尽管它们可能会更改数组的索引或从数组中删除一个原始别名


因此,在调用
change(@a)
的地方,这会在堆栈上放置两个别名,一个是
$a[0]
,另一个是
$a[1]
<代码>更改(%a)更复杂
%a
将其展开为一个交替的键和值列表,其中的值是实际的散列值,修改它们会修改散列中存储的内容,但其中的键只是副本,不再与散列关联。

首先,您将@sigil混淆为表示数组。这实际上是一个列表。调用Change(@a)时,将列表传递给函数,而不是数组对象


哈希的情况略有不同。Perl将您的调用计算为一个列表,并将值作为列表传递。

Perl的子例程将参数作为标量的平面列表接受。作为参数传递的数组实际上也是一个平面列表。甚至一个散列也被视为一个由一个键后跟一个值、后跟一个键等组成的平面列表

除非您明确地这样做,否则平面列表不会作为引用传递。修改
$\u0]
修改
$a[0]
是因为
@
的元素成为作为参数传递的元素的别名。在您的示例中,修改
$\u0]
与修改
$a[0]
相同。但是,尽管这与适用于任何编程语言的“按引用传递”的常见概念大致相似,但这并不是专门传递Perl引用;Perl的引用是不同的(实际上,“引用”是一个重载术语)。别名(在Perl中)是某物的同义词,其中作为引用类似于指向某物的指针

正如perlsyn所说,如果将
@
作为一个整体分配,则会破坏其别名状态。另外请注意,如果尝试修改
$\u0]
,而
$\u0]
恰好是一个文本而不是一个变量,则会出现错误。另一方面,如果调用者的值是可修改的,则修改
$\u0]
会修改调用者的值。因此,在示例一中,更改
$\u0]
$\u1]
会传播回
@a
,因为
@
的每个元素都是
@a
中每个元素的别名

你的第二个例子有点棘手。哈希键是不可变的。除了删除散列键外,Perl没有提供修改散列键的方法。这意味着,
$\u0]
是不可修改的。当您试图修改
$\u0]
时,Perl无法满足该请求。它可能应该发出警告,但没有。您知道,传递给它的平面列表由不可修改的键和可修改的值等组成。这基本上不是问题。我想不出任何理由以您演示的方式修改散列的各个元素;由于散列没有特定的顺序,您无法简单地控制
@
中的哪些元素传播回
%a
中的哪些值

正如您所指出的,正确的协议是传递
\@a
\%a
,这样它们可以被称为
$[0]->{element}
$[0]->[0]
。尽管符号有点复杂,但过了一段时间,它就成了第二天性,而且(在我看来)对发生的事情更加清楚

一定要看一看。特别是:

传入的任何参数都会显示在数组
@
中。因此,如果使用两个参数调用函数,这些参数将存储在
$\u0]
$\u1]
中。数组
@
是本地数组,但其元素是别名
f(@a)
f($a[0], $a[1], $a[2])
f(%h)
f(
   my $k1 = "a", $h{a},
   my $k2 = "b", $h{b}, 
   my $k2 = "c", $h{c}, 
)