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

Mic*_*hel 28 perl

我试图弄清楚Perl子程序及其工作原理.从perlsub我知道子例程是按引用调用的,并且需要一个赋值(例如my(@copy) = @_;)来将它们转换为按值调用.

在下文中,我看到它change被引用称为"a"和"b"变为"x"和"y".但我很困惑为什么数组没有用额外的元素"z"扩展?

use strict;
use Data::Dumper;

my @a = ( "a" ,"b" );

change(@a);

print Dumper(\@a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
    @_[2] = "z";
}
Run Code Online (Sandbox Code Playgroud)

输出:

$VAR1 = [
          'x',
          'y'
        ];
Run Code Online (Sandbox Code Playgroud)

在下面,我传递哈希而不是数组.为什么键不能从"a"变为"x"?

use strict;
use Data::Dumper;

my %a = ( "a" => "b" );

change(%a);

print Dumper(\%a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
}
Run Code Online (Sandbox Code Playgroud)

输出:

$VAR1 = {
    'a' => 'y'
};
Run Code Online (Sandbox Code Playgroud)

我知道真正的解决方案是使用引用传递数组或哈希\@,但我想完全理解这些程序的行为.

ike*_*ami 39

Perl总是通过引用传递.只是有时呼叫者通过了临时标量.

你必须要意识到的第一件事是subs的参数可以是唯一的:scalars列表.*一个不能传递数组或哈希值.评估数组和散列,返回其内容列表.这意味着

f(@a)
Run Code Online (Sandbox Code Playgroud)

是相同的

f($a[0], $a[1], $a[2])
Run Code Online (Sandbox Code Playgroud)

Perl通过引用传递.具体来说,Perl将每个参数别名化为元素@_.修改元素@_将更改返回的标量$a[0]等,从而修改元素@a.

第二个重要的是数组或哈希元素的键确定元素在结构中的存储位置.否则,$a[4]并且$h{k}需要查看数组或散列的每个元素以找到所需的值.这意味着密钥不可修改.移动值需要使用新键创建新元素并删除旧键上的元素.

因此,每当您获得数组或散列的键时,您都会获得密钥的副本.新鲜的标量,可以这么说.

回到问题,

f(%h)
Run Code Online (Sandbox Code Playgroud)

是相同的

f(
   my $k1 = "a", $h{a},
   my $k2 = "b", $h{b}, 
   my $k2 = "c", $h{c}, 
)
Run Code Online (Sandbox Code Playgroud)

@_仍然是别名的返回值%h,但其中一些只是用于持有密钥的临时标量.改变这些将不会产生持久影响.

* - 一些内置插件(例如grep)更像是流控制语句(例如while).它们具有自己的解析规则,因此不限于子的传统模型.

** - 原型可以影响参数列表的计算方式,但它仍然会产生标量列表.

  • @ikegami,关于语句`f(@a)与f($ a [0],$ f [1],$ f [2])`相同.当阵列自动扩展时,此规则有一个例外:http://stackoverflow.com/questions/18091269/array-element-automatically-set-to-undef-wont-change-when-aliased (2认同)

Dav*_*idO 9

Perl的子例程接受参数作为标量的平面列表.作为参数传递的数组实际上也是一个平面列表.即使哈希被视为一个键的平面列表,后跟一个值,后跟一个键,等等.

除非您明确说明,否则不会将平面列表作为参考传递.修改$_[0]修改的事实$a[0]是因为@_作为参数传递的元素的别名元素.修改$_[0]$a[0]示例中的修改相同.但是,虽然这与适用于任何编程语言的"通过引用传递"的常见概念大致相似,但这并不是专门传递Perl引用; Perl的引用是不同的(实际上"引用"是一个重载的术语).别名(在Perl中)是某事物的同义词,其中作为引用类似于指向某事物的指针.

正如perlsyn所说,如果你@_作为一个整体分配,你就会破坏它的别名状态.另请注意,如果您尝试修改$_[0],$_[0]恰好是文字而不是变量,您将收到错误.在另一方面,修改$_[0]不修改调用者的值,如果它可以修改的.因此在示例一中,更改$_[0]$_[1]传播回,@a因为每个元素@_都是每个元素的别名@a.

你的第二个例子有点棘手.散列键是不可变的.除了删除散列键之外,Perl不提供修改散列键的方法.这意味着这$_[0]是不可修改的.当您尝试修改$_[0]Perl时,无法遵守该请求.它可能应该发出警告,但事实并非如此.你看,传递给它的平面列表包括不可修改的密钥,后跟可修改的值等.这主要是一个非问题.我想不出有任何理由以你演示的方式修改哈希的各个元素; 因为哈希没有特定的顺序,所以你不能简单地控制哪些元素@_传播回哪些元素%a.

正如您所指出的,正确的协议是通过\@a\%a,因此它们可以被称为$_[0]->{element}$_[0]->[0].尽管符号稍微复杂一点,但它在一段时间后变成了第二天性,而且(在我看来)对于正在发生的事情更加清晰.

请务必查看perlsub文档.特别是:

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


yst*_*sth 5

(请注意,这use warnings比 更重要use strict。)

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

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

所以在你调用的地方change(@a),这会在堆栈上放置两个别名,一个 to$a[0]和一个 to $a[1]change(%a)比较复杂;%a展平为键和值的交替列表,其中值是实际的哈希值,修改它们会修改存储在哈希中的内容,但键只是副本,不再与哈希相关联。