路过

通过复制或引用分配任务?

分配时,perl通过值复制,参见以下代码

my $var1 = "not modified";

my $var2 = $var1; # Copy the content, not the variable itself
$var1 = "modified";

print "$var2\n";

此代码将打印初始的未修改

not modified

这是因为赋值是复制变量的内容,而不是别名变量。

要检查变量的标识,我们应该检查它们的引用

关于引用的说明

一个引用表示一个包含值存储位置信息的标量值(某种程度上)。拥有相同的存储位置意味着它是相同的变量(或别名)。引用的字符串表示形式甚至给出了它所指向的值的类型(标量、数组、哈希等)的详细信息。就像下面刚刚看到的SCALAR(...)

SCALAR(0x...)

如果你只对指向的类型感兴趣,你可以通过ref运算符获取这些信息(例如,用于类型检查)

my @array = ();
print ref(\@array) . "\n";

这将给出

ARRAY

你可以通过反斜杠运算符和引号(例如$)或->来获取(创建)引用

my $str = "foo";

# Get reference
my $ref = \$str;

# Dereference with $
print "$$ref\n";

或者

my @array = ( "foo", "bar", "baz" );

# Get reference
my $ref = \@array;

# Derefrence with ->
print "$ref->[0]\n";

两者都将打印

foo

你也可以通过符号引用来通过其名称构建引用

my $name = "var";

# Store in $var
$$name = "bazinga";

print "$var\n";

这将打印

bazinga

或者,你可以使用类似这样的“匿名”运算符[]{}

my @characters1 = ( "Sheldon", "Leonard", "Penny" );
my @characters2 = ( "Howard", "Rajesh", "Bernadette", "Amy" );
my @big_bang_theory = ();

push @big_bang_theory, [@characters1];
push @big_bang_theory, [@characters2];

这样,数组引用被推入,但它指向的是数组(因此它打破了@characters1/@characters2@big_bang_theory内容之间的联系)。

或者,你甚至可以获取一个引用的引用,然后多次取消引用。这允许你产生奇怪/愚蠢的东西,如下所示

my $str = "bazinga";

# Get a ref on a ref on a ref on a...
my $rrrrrrrrrref = \\\\\\\\\\$str;

# De-de-de-...-de-dereference
print "$$$$$$$$$$$rrrrrrrrrref\n";

或者

my $str = "bazinga";

# De-de-de-...-de-dereference a re-re-re-re-...-ref
print $$$$$$$$$${\\\\\\\\\\$str} . "\n";

但不要这样做。

检查引用

回到我们的变量,你可以通过打印它们的引用来检查它们的“身份”

# Compare storage location
print \$var1 . " vs " . \$var2 . "\n";

这给出了2个不同的存储位置

SCALAR(0x55589c6378e0) vs SCALAR(0x55589c6378f8)
                   ^^                        ^^

因此,它清楚地表明这些是两个不同的变量。

但是等等,还有一些情况下赋值实际上是别名的,这就是在foreach循环中发生的情况

my @array = ( "foo", "bar", "baz" );

foreach my $var (@array) {
    $var = "modified"; # Modify the initial @array
}

print "@array\n";

然后数组就得到了很好的修改

modified modified modified

当我们讨论“检查引用”的话题时,你可以使用像Devel::Peek这样的模块或类似工具来获取引用(或任何变量)的内部细节。你将得到比你想要的更多的信息!

按引用传递还是按值传递?

我们已经讨论了赋值,但参数是如何传递给子例程的?

答案:它们始终是“按引用传递”,但最好说“别名”,以避免与之前讨论的引用概念混淆。

这个事实远非显而易见,因为你将在下一个示例中看到,将参数检索到新变量中(因此是复制赋值)通常会让你相信Perl是“按值传递”的。

子例程中的变量修改

在子例程内部,根据你获取/使用参数的方式,修改变量通常不会影响参数。

sub bycopy {        
    my $cp = shift; # /!\ copy is done here (not during call) from aliased argument(s)
    # my $cp = (@_); # Same with this
    $cp = "modified";
}

my $var = "not modified";
bycopy($var);

print "$var\n";

这将打印

not modified

修改在外部不可见,因为为了传播更改,它应该被返回给调用者return $cp;并且通过$var = bycopy($var)赋值(实际上由于“默认变量魔法”,返回是可选的)。

显然,这种限制通常是个好主意(想想副作用),但如果你有意在调用者作用域中修改变量,则不是这样。

为了能够在子例程内部修改变量,使其传递给调用者,我可以使用引用。

sub byref {
    my $ref = shift;
    $$ref = "modified";
}

my $variable = "not modified";
byref(\$variable);

print "$variable\n";

这将打印

modified

这样,通过一些技巧,我可以通过引用传递变量本身,无论它是什么(标量、数组、散列……),我可以编辑它,并且它会保持不变。

但有一个更简单且明显的方法,默认数组 @_ 实际上是已经别名了!

sub bydefaultarray {
    $_[0] = "modified";
}

my $variable = "not modified";
bydefaultarray($variable);

print "$variable\n";

并且它已经被正确修改了

modified

你只需要直接在 @_(或小心制作的别名)上工作,不要使用像 shift 或任何只会给你 副本 的赋值操作。

使用原型的引用隐式转换

然后,在子例程和引用周围还有一些东西需要了解:原型的使用。原型改变了 Perl 的行为,并允许例如子例程接收一个数组并将其隐式转换为引用。

以下示例强制将数组转换为引用而不是将其展开

sub proto(\@) {
    my $ref = shift; # It is the array ref, not the first item of @array
    print "$ref\n";
    $ref->[0] = "dog";
    $ref->[1] = "cat";
    $ref->[2] = "pig";
}

my @array = ("foo", "bar", "baz");

# Array flattening won't occur but instead a reference will be passed
proto(@array); # proto(\@array);

print "@array\n";

输出如下

ARRAY(0x55ac34f261c0)
dog cat pig

别名

有方法可以在不系统重建引用的情况下别名变量。例如,你可以使用模块 Data::Alias

use Data::Alias;

my $variable = "not modified";

# Alias!
alias $alias = $variable;
$variable = "modified";

print "$alias\n";

并且打印得很好

modified

最近的 Perl 也带来了一些 别名功能(无需处理符号表)

use feature qw/refaliasing declared_refs/;
no warnings qw/experimental::refaliasing experimental::declared_refs/;

my $variable = "not modified";
my \$alias = \$variable; # Assigning to reference
$variable = "modified";

print "$alias\n";

这将打印

modified

并且打印的引用值进行比较

print \$alias . " equals " . \$variable . "\n";

并且它们指向同一件事情

SCALAR(0x5637ffc833a0) equals SCALAR(0x5637ffc833a0)

别名也是性能问题,这些技巧被大量用于此目的。

最近的 Perl 带来了 Copy-On-Write 功能,请参阅 perl 5.20.0 性能增强perlguts COW

多亏了 COW,当我们赋值时,我们得到两个不同的变量,但只有在需要时才实际复制值,这可能产生显著的性能提升。

标签

Thibault Duponchelle

Thibault Duponchelle 是一名软件开发人员。主要兴趣在于 GNU/Linux、开源、Perl、C 和汇编。

浏览他们的文章

反馈

这篇文章有什么问题吗?通过在 GitHub 上打开问题或拉取请求来帮助我们。