内容

名称

perlref - Perl 引用和嵌套数据结构

注意

这是关于引用所有方面的完整文档。有关仅包含基本功能的简短教程介绍,请参见 perlreftut

描述

在 Perl 5 版本发布之前,很难表示复杂的数据结构,因为所有引用都必须是符号的 - 即使那样,也很难引用变量而不是符号表条目。Perl 现在不仅使使用指向变量的符号引用变得更容易,而且还允许您对任何数据或代码片段进行“硬”引用。任何标量都可以保存硬引用。因为数组和哈希包含标量,所以您现在可以轻松地构建数组的数组、数组的哈希、哈希的数组、数组的哈希的函数等等。

硬引用很智能——它们会为你跟踪引用计数,并在引用计数降至零时自动释放被引用的对象。(自引用或循环数据结构中值的引用计数可能不会在没有一点帮助的情况下降至零;有关详细说明,请参阅 "循环引用"。)如果该对象恰好是一个对象,则该对象会被析构。有关对象的更多信息,请参阅 perlobj。(从某种意义上说,Perl 中的一切都是对象,但我们通常将这个词保留给已正式“祝福”到类包中的对象的引用。)

符号引用是变量或其他对象的名称,就像 Unix 文件系统中的符号链接只包含一个文件的名称一样。*glob 表示法有点像符号引用。(符号引用有时被称为“软引用”,但请不要这样称呼它们;引用已经足够令人困惑了,没有必要再用无用的同义词。)

相比之下,硬引用更像是 Unix 文件系统中的硬链接:它们用于访问底层对象,而不必关心它的(其他)名称。当“引用”这个词在没有形容词的情况下使用时,就像下一段中那样,它通常指的是硬引用。

在 Perl 中使用引用很容易。只有一个首要原则:一般来说,Perl 不会进行隐式引用或解引用。当一个标量持有引用时,它始终表现为一个简单的标量。它不会神奇地开始成为数组、哈希或子程序;你必须明确地告诉它这样做,通过解引用它。

创建引用

引用可以通过多种方式创建。

反斜杠运算符

通过对变量、子程序或值使用反斜杠运算符。(这与 C 中的 &(地址运算符)非常类似。)这通常会创建对变量的另一个引用,因为符号表中已经存在对该变量的引用。但符号表引用可能会消失,而你仍然拥有反斜杠返回的引用。以下是一些示例

$scalarref = \$foo;
$arrayref  = \@ARGV;
$hashref   = \%ENV;
$coderef   = \&handler;
$globref   = \*foo;

无法使用反斜杠运算符创建对 IO 处理程序(文件句柄或目录句柄)的真正引用。你最多只能获得对类型全局变量的引用,它实际上是一个完整的符号表条目。但请参阅下面对 *foo{THING} 语法的解释。但是,你仍然可以使用类型全局变量和全局引用,就好像它们是 IO 处理程序一样。

方括号

可以使用方括号创建对匿名数组的引用

$arrayref = [1, 2, ['a', 'b', 'c']];

这里我们创建了一个指向三个元素的匿名数组的引用,其中最后一个元素本身指向另一个包含三个元素的匿名数组的引用。(稍后描述的多维语法可用于访问此数组。例如,在上述代码之后,$arrayref->[2][1] 的值为 "b"。)

对枚举列表进行引用与使用方括号不同,它等同于创建引用列表!

@list = (\$a, \@b, \%c);
@list = \($a, @b, %c);      # same thing!

作为特殊情况,\(@foo) 返回一个指向 @foo 内容的引用列表,而不是指向 @foo 本身的引用。%foo 也是如此,只是键引用指向副本(因为键只是字符串,而不是完整的标量)。

花括号

可以使用花括号创建指向匿名哈希的引用

$hashref = {
    'Adam'  => 'Eve',
    'Clyde' => 'Bonnie',
};

像这样的匿名哈希和数组构造器可以自由混合,以生成您想要的任何复杂结构。下面描述的多维语法也适用于这些构造器。上面的值是字面量,但变量和表达式也可以正常工作,因为 Perl 中的赋值运算符(即使在 local()my() 中)是可执行语句,而不是编译时声明。

由于花括号(大括号)用于其他几种用途,包括 BLOCK,因此您可能偶尔需要在语句开头对花括号进行消歧,在前面加上 +return,以便 Perl 意识到开头的花括号不是开始一个 BLOCK。使用花括号的经济性和助记价值被认为值得这种偶尔的额外麻烦。

例如,如果您希望一个函数创建一个新哈希并返回指向它的引用,您可以使用以下选项

sub hashem {        { @_ } }   # silently wrong
sub hashem {       +{ @_ } }   # ok
sub hashem { return { @_ } }   # ok

另一方面,如果您想要其他含义,您可以这样做

sub showem {        { @_ } }   # ambiguous (currently ok,
                               # but may change)
sub showem {       {; @_ } }   # ok
sub showem { { return @_ } }   # ok

开头的 +{{; 始终用于对表达式进行消歧,使其表示 HASH 引用或 BLOCK。

匿名子程序

可以使用 sub 而不带子程序名称来创建指向匿名子程序的引用

$coderef = sub { print "Boink!\n" };

请注意分号。除了内部代码不会立即执行之外,sub {} 不太像声明,更像是一个运算符,比如 do{}eval{}。(但是,无论您执行该特定行多少次(除非您在 eval("...") 中),$coderef 仍然会指向同一个匿名子程序的引用。)

匿名子程序在 my() 变量方面充当闭包,即当前作用域内词法可见的变量。闭包是 Lisp 世界中的一个概念,它说如果你在特定的词法上下文中定义了一个匿名函数,它会假装在该上下文中运行,即使它是在该上下文之外调用的。

用人话说,这是一种在定义子程序和调用子程序时传递参数的有趣方式。它对于设置稍后运行的小段代码很有用,例如回调。你甚至可以用它来做面向对象的事情,尽管 Perl 已经提供了另一种机制来做到这一点——参见 perlobj

你也可以将闭包视为一种在不使用 eval() 的情况下编写子程序模板的方法。以下是一个关于闭包如何工作的简单示例

sub newprint {
    my $x = shift;
    return sub { my $y = shift; print "$x, $y!\n"; };
}
$h = newprint("Howdy");
$g = newprint("Greetings");

# Time passes...

&$h("world");
&$g("earthlings");

这将打印

Howdy, world!
Greetings, earthlings!

特别要注意的是,$x 仍然引用传递给 newprint() 的值,尽管“my $x”在匿名子程序运行时已经超出作用域。这就是闭包的全部意义所在。

顺便说一下,这仅适用于词法变量。动态变量将继续像往常一样工作。闭包不是大多数 Perl 程序员一开始就需要担心的事情。

构造函数

引用通常由称为构造函数的特殊子程序返回。Perl 对象只是对一种特殊类型的对象的引用,这种对象恰好知道它与哪个包相关联。构造函数只是知道如何创建这种关联的特殊子程序。它们通过从一个普通的引用开始来做到这一点,即使它也是一个对象,它仍然是一个普通的引用。构造函数通常命名为 new()。你可以间接调用它们

$objref = new Doggie( Tail => 'short', Ears => 'long' );

但这在某些情况下会导致语法模糊,因此通常最好使用直接方法调用方法

$objref   = Doggie->new(Tail => 'short', Ears => 'long');

use Term::Cap;
$terminal = Term::Cap->Tgetent( { OSPEED => 9600 });

use Tk;
$main    = MainWindow->new();
$menubar = $main->Frame(-relief              => "raised",
                        -borderwidth         => 2)

这种间接对象语法只有在 use feature "indirect" 生效时才可用,而当请求 use v5.36(或更高版本)时,情况并非如此,最好完全避免间接对象语法。

自动生成

如果在假设它们存在的情况下取消引用它们,则适当类型的引用会突然出现。因为我们还没有讨论取消引用,所以我们还不能向你展示任何示例。

类型全局槽

可以使用一种特殊的语法来创建引用,这种语法被称为 *foo{THING} 语法。*foo{THING} 返回 *foo 中 THING 槽的引用(*foo 是包含所有名为 foo 的内容的符号表条目)。

$scalarref = *foo{SCALAR};
$arrayref  = *ARGV{ARRAY};
$hashref   = *ENV{HASH};
$coderef   = *handler{CODE};
$ioref     = *STDIN{IO};
$globref   = *foo{GLOB};
$formatref = *foo{FORMAT};
$globname  = *foo{NAME};    # "foo"
$pkgname   = *foo{PACKAGE}; # "main"

大多数语法都是不言自明的,但 *foo{IO} 需要特别注意。它返回 IO 句柄,用于文件句柄("perlfunc 中的 open")、套接字("perlfunc 中的 socket""perlfunc 中的 socketpair")以及目录句柄("perlfunc 中的 opendir")。为了与早期版本的 Perl 保持兼容,*foo{FILEHANDLE}*foo{IO} 的同义词,但建议不要使用它,以鼓励一致地使用一个名称:IO。在 Perl v5.8 到 v5.22 之间的版本中,它会发出弃用警告,但此弃用警告已被撤销。

如果特定 THING 尚未使用,*foo{THING} 会返回 undef,但标量除外。如果 $foo 尚未使用,*foo{SCALAR} 会返回对匿名标量的引用。这可能会在将来的版本中发生变化。

*foo{NAME}*foo{PACKAGE} 是例外,它们返回字符串而不是引用。它们返回类型全局变量本身的包和名称,而不是分配给它的名称。因此,在 *foo=*Foo::bar 之后,*foo 作为字符串使用时将变为 "*Foo::bar",但 *foo{PACKAGE}*foo{NAME} 将继续分别生成 "main" 和 "foo"。

*foo{IO}"perldata 中的 Typeglobs 和文件句柄" 中给出的 *HANDLE 机制的替代方案,用于将文件句柄传递进出子程序或存储到更大的数据结构中。它的缺点是它不会为你创建一个新的文件句柄。它的优点是,你用类型全局变量赋值时,有较小的风险破坏超过你想要破坏的内容。(不过,它仍然将文件句柄和目录句柄混为一谈。)但是,如果你将传入的值分配给标量而不是类型全局变量,就像我们在下面的示例中所做的那样,就不会有这种风险。

splutter(*STDOUT);          # pass the whole glob
splutter(*STDOUT{IO});      # pass both file and dir handles

sub splutter {
    my $fh = shift;
    print $fh "her um well a hmmm\n";
}

$rec = get_rec(*STDIN);     # pass the whole glob
$rec = get_rec(*STDIN{IO}); # pass both file and dir handles

sub get_rec {
    my $fh = shift;
    return scalar <$fh>;
}

使用引用

关于创建引用就讲到这里了。现在你可能迫不及待地想知道如何使用引用来获取你丢失已久的数据。有几种基本方法。

简单标量

在任何你放置标识符(或标识符链)作为变量或子程序名称的一部分的地方,你可以用一个包含正确类型引用的简单标量变量替换标识符。

$bar = $$scalarref;
push(@$arrayref, $filename);
$$arrayref[0] = "January";
$$hashref{"KEY"} = "VALUE";
&$coderef(1,2,3);
print $globref "output\n";

重要的是要理解,我们在这里不是$arrayref[0]$hashref{"KEY"}进行解引用。标量变量的解引用发生在它进行任何键查找之前。任何比简单标量变量更复杂的东西都必须使用下面的方法 2 或 3。但是,“简单标量”包括一个标识符,它本身递归地使用方法 1。因此,以下代码将打印“howdy”。

$refrefref = \\\"howdy";
print $$$$refrefref;

代码块

在任何你放置标识符(或标识符链)作为变量或子程序名称的一部分的地方,你可以用一个返回正确类型引用的代码块替换标识符。换句话说,前面的例子可以这样写

$bar = ${$scalarref};
push(@{$arrayref}, $filename);
${$arrayref}[0] = "January";
${$hashref}{"KEY"} = "VALUE";
&{$coderef}(1,2,3);
$globref->print("output\n");  # iff IO::Handle is loaded

诚然,在这种情况下使用花括号有点傻,但代码块可以包含任何任意的表达式,特别是带下标的表达式

&{ $dispatch{$index} }(1,2,3);      # call correct routine

由于能够省略$$x的简单情况下的花括号,人们经常犯将解引用符号视为适当运算符的错误,并想知道它们的优先级。如果它们是运算符,那么你可以使用括号而不是花括号。但事实并非如此。考虑下面的区别;情况 0 是情况 1 的简写版本,而不是情况 2

$$hashref{"KEY"}   = "VALUE";       # CASE 0
${$hashref}{"KEY"} = "VALUE";       # CASE 1
${$hashref{"KEY"}} = "VALUE";       # CASE 2
${$hashref->{"KEY"}} = "VALUE";     # CASE 3

情况 2 也具有欺骗性,因为你正在访问一个名为 %hashref 的变量,而不是通过 $hashref 解引用到它可能引用的哈希。那将是情况 3。

箭头符号

子程序调用和单个数组元素的查找经常出现,以至于使用方法 2 变得很麻烦。作为一种语法糖,方法 2 的例子可以写成

$arrayref->[0] = "January";   # Array element
$hashref->{"KEY"} = "VALUE";  # Hash element
$coderef->(1,2,3);            # Subroutine call

箭头的左侧可以是任何返回引用的表达式,包括之前的解引用。请注意,$array[$x]$array->[$x]相同

$array[$x]->{"foo"}->[0] = "January";

这是我们之前提到的引用可以在左值上下文中自动创建的情况之一。在此语句之前,$array[$x]可能未定义。如果是这样,它将自动定义为一个哈希引用,以便我们可以在其中查找{"foo"}。同样,$array[$x]->{"foo"}将自动定义为一个数组引用,以便我们可以在其中查找[0]。这个过程称为自动生成

还有一点。箭头在方括号下标之间是可选的,因此你可以将上面的代码缩短为

$array[$x]{"foo"}[0] = "January";

在只使用普通数组的退化情况下,这会给你像 C 的多维数组一样的东西

$score[$x][$y][$z] += 42;

好吧,实际上,它并不完全像 C 的数组。C 不知道如何按需扩展它的数组。Perl 可以。

对象

如果一个引用恰好是指向一个对象,那么可能存在访问所引用内容的方法,并且你应该坚持使用这些方法,除非你是在定义该对象方法的类包中。换句话说,要友好,不要在没有充分理由的情况下违反对象的封装。Perl 不强制执行封装。我们不是极权主义者。不过,我们确实期望一些基本的礼貌。

其他用法

使用字符串或数字作为引用会产生符号引用,如上所述。使用引用作为数字会产生一个整数,表示它在内存中的存储位置。这样做唯一有用的事情是将两个引用进行数值比较,以查看它们是否指向相同的位置。

if ($ref1 == $ref2) {  # cheap numeric compare of references
    print "refs 1 and 2 refer to the same thing\n";
}

使用引用作为字符串会产生其引用的类型,包括任何包祝福,如 perlobj 中所述,以及以十六进制表示的数字地址。ref() 运算符只返回引用指向的类型,不包括地址。有关其使用细节和示例,请参见 "ref" in perlfunc

bless() 运算符可用于将引用指向的对象与充当对象类的包关联。参见 perlobj

类型全局变量可以像引用一样解引用,因为解引用语法始终指示所需的引用类型。因此,${*foo}${\$foo} 都表示相同的标量变量。

以下是一个将子程序调用插入字符串的技巧

print "My sub returned @{[mysub(1,2,3)]} that time.\n";

它的工作原理是,当在双引号字符串中看到 @{...} 时,它会被评估为一个代码块。该代码块创建对包含 mysub(1,2,3) 调用结果的匿名数组的引用。因此,整个代码块返回对数组的引用,然后由 @{...} 解引用并插入到双引号字符串中。这种技巧对于任意表达式也很有用

print "That yields @{[$n + 5]} widgets\n";

类似地,返回对标量的引用的表达式可以通过 ${...} 解引用。因此,上面的表达式可以写成

print "That yields ${\($n + 5)} widgets\n";

循环引用

在 Perl 中可以创建“循环引用”,这会导致内存泄漏。循环引用发生在两个引用相互引用时,例如

my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;

您也可以使用单个变量创建循环引用。

my $foo;
$foo = \$foo;

在这种情况下,变量的引用计数永远不会达到 0,并且引用永远不会被垃圾回收。这会导致内存泄漏。

因为 Perl 中的对象是作为引用实现的,所以也可以使用对象创建循环引用。想象一个 TreeNode 类,其中每个节点都引用其父节点和子节点。任何具有父节点的节点都将是循环引用的一部分。

您可以通过创建“弱引用”来打破循环引用。弱引用不会增加变量的引用计数,这意味着对象可以超出范围并被销毁。您可以使用 Scalar::Util 模块导出的 weaken 函数来削弱引用,或者在 Perl 版本 5.35.7 或更高版本中直接作为 builtin::weaken 使用。

以下是如何使第一个示例更安全

use Scalar::Util 'weaken';

my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;

weaken $foo->{bar};

$foo$bar 的引用已被削弱。当 $bar 变量超出范围时,它将被垃圾回收。下次您查看 $foo->{bar} 键的值时,它将是 undef

这种远程操作可能会令人困惑,因此您在使用 weaken 时要小心。您应该在将要首先超出范围的变量中削弱引用。这样,寿命更长的变量将包含预期的引用,直到它超出范围。

符号引用

我们说过,如果引用未定义,它们会根据需要出现,但我们没有说如果用作引用的值已定义,但不是硬引用,会发生什么。如果您将其用作引用,它将被视为符号引用。也就是说,标量的值被认为是变量的名称,而不是指向(可能是)匿名值的直接链接。

人们经常期望它像这样工作。所以它确实如此。

$name = "foo";
$$name = 1;                 # Sets $foo
${$name} = 2;               # Sets $foo
${$name x 2} = 3;           # Sets $foofoo
$name->[0] = 4;             # Sets $foo[0]
@$name = ();                # Clears @foo
&$name();                   # Calls &foo()
$pack = "THAT";
${"${pack}::$name"} = 5;    # Sets $THAT::foo without eval

这很强大,也略带危险,因为有可能(以最大的真诚)打算使用硬引用,而意外地使用符号引用。为了防止这种情况,您可以说

use strict 'refs';

然后,在封闭块的其余部分中,只允许硬引用。内部块可能会用以下命令抵消该命令

no strict 'refs';

只有包变量(全局变量,即使是局部变量)对符号引用可见。词法变量(用 my() 声明)不在符号表中,因此对这种机制不可见。例如

local $value = 10;
$ref = "value";
{
    my $value = 20;
    print $$ref;
}

这仍然会打印 10,而不是 20。请记住,local() 会影响包变量,而包变量对整个包都是“全局”的。

非符号引用

符号引用周围的方括号可以简单地用于将标识符或变量名与表达式中的其他部分隔离开来,就像它们一直以来在字符串中所做的那样。例如,

$push = "pop on ";
print "${push}over";

一直以来都意味着打印“pop on over”,即使 push 是一个保留字。这被推广到在没有包含的双引号的情况下也能以相同的方式工作,因此

print ${push} . "over";

甚至

print ${ push } . "over";

将具有相同的效果。当使用严格引用时,此结构被视为符号引用

use strict 'refs';
${ bareword };      # Okay, means $bareword.
${ "bareword" };    # Error, symbolic reference.

同样,由于所有使用单个单词进行的下标操作,相同的规则适用于任何用于对哈希进行下标操作的裸字。因此,现在,您不必再写

$hash{ "aaa" }{ "bbb" }{ "ccc" }

您可以只写

$hash{ aaa }{ bbb }{ ccc }

而不必担心下标是否为保留字。在您确实希望执行类似操作的极少数情况下

$hash{ shift }

您可以通过添加任何使其成为裸字以外的东西来强制解释为保留字

$hash{ shift() }
$hash{ +shift }
$hash{ shift @_ }

use warnings 编译指示或-w 开关会在将保留字解释为字符串时发出警告。但它不再会警告您使用小写单词,因为字符串实际上是被引用的。

伪哈希:使用数组作为哈希

伪哈希已从 Perl 中移除。'fields' 编译指示仍然可用。

函数模板

如上所述,匿名函数可以访问编译该函数时可见的词法变量,从而创建一个闭包。它保留对这些变量的访问权限,即使它直到稍后才运行,例如在信号处理程序或 Tk 回调中。

使用闭包作为函数模板,我们可以生成许多行为类似的函数。假设您想要以颜色命名的函数,这些函数为各种颜色生成 HTML 字体更改

print "Be ", red("careful"), "with that ", green("light");

red() 和 green() 函数将是相似的。为了创建这些函数,我们将一个闭包分配给我们试图构建的函数名称的类型全局变量。

@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
    no strict 'refs';       # allow symbol table manipulation
    *$name = *{uc $name} = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

现在所有这些不同的函数似乎独立存在。您可以调用 red()、RED()、blue()、BLUE()、green() 等。这种技术既节省了编译时间和内存使用,而且由于语法检查在编译时发生,因此错误更少。匿名子程序中的任何变量都必须是词法变量,才能创建正确的闭包。这就是循环迭代变量上使用 my 的原因。

这是为闭包提供原型最有意义的少数地方之一。如果您想对这些函数的参数施加标量上下文(对于这个特定示例来说可能不是明智之举),您可以改为这样编写

*$name = sub ($) { "<FONT COLOR='$name'>$_[0]</FONT>" };

但是,由于原型检查在编译时发生,因此上面的赋值发生得太晚,没有多大用处。您可以通过将整个赋值循环放在 BEGIN 块中来解决这个问题,迫使它在编译期间发生。

对随时间变化的词法变量的访问——比如上面 for 循环中的那些,基本上是周围词法作用域中元素的别名——只适用于匿名子程序,不适用于命名子程序。一般来说,命名子程序不能正确嵌套,应该只在主包作用域中声明。

这是因为命名子程序在编译时创建,因此它们的词法变量被分配给父块第一次执行时的父词法变量。如果第二次进入父作用域,它的词法变量将再次创建,而嵌套的子程序仍然引用旧的词法变量。

匿名子程序每次执行 sub 运算符时都会捕获,因为它们是在运行时创建的。如果您习惯在其他编程语言中使用具有私有变量的嵌套子程序,那么您需要在 Perl 中稍微努力一下。这种类型的直观编码会导致关于“不会保持共享”的神秘警告,原因如上所述。例如,这将不起作用

sub outer {
    my $x = $_[0] + 35;
    sub inner { return $x * 19 }   # WRONG
    return $x + inner();
}

解决方法如下

sub outer {
    my $x = $_[0] + 35;
    local *inner = sub { return $x * 19 };
    return $x + inner();
}

现在,inner() 只能从 outer() 内部调用,因为匿名子程序的临时赋值。但当它这样做时,它可以正常访问 outer() 作用域中 $x 词法变量,在调用 outer 时。

这产生了有趣的效应,即创建了一个对另一个函数局部的函数,这在 Perl 中通常不支持。

后缀解引用语法

从 v5.20.0 开始,可以使用后缀语法来使用引用。它的行为如 "使用引用" 中所述,但使用后缀符号和星号而不是前缀符号。

例如

$r = \@a;
@b = $r->@*; # equivalent to @$r or @{ $r }

$r = [ 1, [ 2, 3 ], 4 ];
$r->[1]->@*;  # equivalent to @{ $r->[1] }

在 Perl 5.20 和 5.22 中,此语法必须使用 use feature 'postderef' 启用。从 Perl 5.24 开始,无需任何功能声明即可使用它。

后缀解引用应该在块(环绕)解引用起作用的所有情况下都起作用,并且应该完全等效。此语法允许完全从左到右编写和读取解引用。定义以下等效性

$sref->$*;  # same as  ${ $sref }
$aref->@*;  # same as  @{ $aref }
$aref->$#*; # same as $#{ $aref }
$href->%*;  # same as  %{ $href }
$cref->&*;  # same as  &{ $cref }
$gref->**;  # same as  *{ $gref }

特别注意 $cref->&* 等效于 $cref->(),并且可以用于不同的目的。

可以通过后缀解引用功能提取全局变量元素

$gref->*{SCALAR}; # same as *{ $gref }{SCALAR}

后缀数组和标量解引用可以用于插值字符串(双引号或 qq 运算符),但前提是启用了 postderef_qq 功能。当启用了 postderef_qq 功能时,也支持插值后缀数组最高索引访问 (->$#*)。

后缀引用切片

数组和哈希的值切片也可以使用后缀解引用符号来获取,具有以下等效性

$aref->@[ ... ];  # same as @$aref[ ... ]
$href->@{ ... };  # same as @$href{ ... }

后缀键/值对切片,在 5.20.0 中添加并在 perldata 的键/值哈希切片部分 中有说明,也按预期工作

$aref->%[ ... ];  # same as %$aref[ ... ]
$href->%{ ... };  # same as %$href{ ... }

与后缀数组一样,后缀值切片解引用可以用于插值字符串(双引号或 qq 运算符),但前提是启用了 postderef_qq 功能

分配给引用

从 v5.22.0 开始,可以将引用运算符分配给。它执行一个别名操作,以便左侧引用的变量名称成为右侧引用的内容的别名

\$a = \$b; # $a and $b now point to the same scalar
\&foo = \&bar; # foo() now means bar()

此语法必须使用 use feature 'refaliasing' 启用。它处于实验阶段,默认情况下会发出警告,除非 no warnings 'experimental::refaliasing' 生效。

这些形式可以分配给,并导致右侧在标量上下文中进行评估

\$scalar
\@array
\%hash
\&sub
\my $scalar
\my @array
\my %hash
\state $scalar # or @array, etc.
\our $scalar   # etc.
\local $scalar # etc.
\local our $scalar # etc.
\$some_array[$index]
\$some_hash{$key}
\local $some_array[$index]
\local $some_hash{$key}
condition ? \$this : \$that[0] # etc.

切片操作和括号导致右侧在列表上下文中进行评估

\@array[5..7]
(\@array[5..7])
\(@array[5..7])
\@hash{'foo','bar'}
(\@hash{'foo','bar'})
\(@hash{'foo','bar'})
(\$scalar)
\($scalar)
\(my $scalar)
\my($scalar)
(\@array)
(\%hash)
(\&sub)
\(&sub)
\($foo, @bar, %baz)
(\$foo, \@bar, \%baz)

右侧的每个元素都必须是对正确类型数据的引用。直接围绕数组(以及可能还包括 my/state/our/local)的括号将使数组的每个元素成为对右侧引用的相应标量的别名

\(@a) = \(@b); # @a and @b now have the same elements
\my(@a) = \(@b); # likewise
\(my @a) = \(@b); # likewise
push @a, 3; # but now @a has an extra element that @b lacks
\(@a) = (\$a, \$b, \$c); # @a now contains $a, $b, and $c

将该形式与 local 结合起来,并在哈希周围直接放置括号是禁止的(因为不清楚它们应该做什么)

\local(@array) = foo(); # WRONG
\(%hash)       = bar(); # WRONG

可以将对引用和非引用的分配组合在列表和条件三元表达式中,只要右侧的值是左侧每个元素的正确类型,尽管这可能会导致代码变得模糊

(my $tom, \my $dick, \my @harry) = (\1, \2, [1..3]);
# $tom is now \1
# $dick is now 2 (read-only)
# @harry is (1,2,3)

my $type = ref $thingy;
($type ? $type eq 'ARRAY' ? \@foo : \$bar : $baz) = $thingy;

foreach 循环也可以为其循环变量使用引用构造函数,但语法仅限于以下之一,并在反斜杠后可选地使用 mystateour

\$s
\@a
\%h
\&c

不允许使用括号。此功能对于数组的数组或数组的哈希特别有用。

foreach \my @a (@array_of_arrays) {
    frobnicate($a[0], $a[-1]);
}

foreach \my %h (@array_of_hashes) {
    $h{gelastic}++ if $h{type} eq 'funny';
}

警告:别名在闭包中无法正常工作。如果您尝试从内部子例程或 eval 中别名化词法变量,则别名仅在该内部子例程中可见,不会影响声明变量的外部子例程。这种奇怪的行为可能会发生变化。

声明对变量的引用

从 v5.26.0 开始,引用运算符可以在 mystateourlocal 之后出现。此语法必须使用 use feature 'declared_refs' 启用。它处于实验阶段,默认情况下会发出警告,除非 no warnings 'experimental::refaliasing' 生效。

此功能使这些

my \$x;
our \$y;

等效于

\my $x;
\our $x;

它主要用于对引用的赋值(参见上面的 "对引用的赋值")。它还允许在声明的变量列表中仅对某些项目使用反斜杠。

my ($foo, \@bar, \%baz); # equivalent to:  my $foo, \my(@bar, %baz);

警告:不要将引用用作哈希键

您不能(有效地)将引用用作哈希的键。它将被转换为字符串。

$x{ \$a } = $a;

如果您尝试取消引用键,它不会进行硬取消引用,并且您将无法完成您尝试的操作。您可能想做一些更像

$r = \@a;
$x{ $r } = $r;

然后至少您可以使用 values(),它将是真正的引用,而不是 keys(),它不会是。

标准 Tie::RefHash 模块为此提供了一个方便的解决方法。

另请参阅

除了显而易见的文档之外,源代码可能具有指导意义。在 Perl 源代码目录中的 t/op/ref.t 回归测试中可以找到一些关于引用使用的病态示例。

另请参阅 perldscperllol,了解如何使用引用创建复杂的数据结构,以及 perlootutperlobj,了解如何使用它们创建对象。