内容

名称

perlsub - Perl 子程序

概要

声明子程序

sub NAME;                       # A "forward" declaration.
sub NAME(PROTO);                #  ditto, but with prototypes
sub NAME : ATTRS;               #  with attributes
sub NAME(PROTO) : ATTRS;        #  with attributes and prototypes

sub NAME BLOCK                  # A declaration and a definition.
sub NAME(PROTO) BLOCK           #  ditto, but with prototypes
sub NAME : ATTRS BLOCK          #  with attributes
sub NAME(PROTO) : ATTRS BLOCK   #  with prototypes and attributes

use feature 'signatures';
sub NAME(SIG) BLOCK                     # with signature
sub NAME :ATTRS (SIG) BLOCK             # with signature, attributes
sub NAME :prototype(PROTO) (SIG) BLOCK  # with signature, prototype

在运行时定义匿名子程序

$subref = sub BLOCK;                    # no proto
$subref = sub (PROTO) BLOCK;            # with proto
$subref = sub : ATTRS BLOCK;            # with attributes
$subref = sub (PROTO) : ATTRS BLOCK;    # with proto and attributes

use feature 'signatures';
$subref = sub (SIG) BLOCK;          # with signature
$subref = sub : ATTRS(SIG) BLOCK;   # with signature, attributes

导入子程序

use MODULE qw(NAME1 NAME2 NAME3);

调用子程序

NAME(LIST);     # & is optional with parentheses.
NAME LIST;      # Parentheses optional if predeclared/imported.
&NAME(LIST);    # Circumvent prototypes.
&NAME;          # Makes current @_ visible to called subroutine.

描述

与许多语言一样,Perl 提供了用户定义的子程序。这些子程序可以位于主程序中的任何位置,通过 dorequireuse 关键字从其他文件加载,或使用 eval 或匿名子程序动态生成。您甚至可以使用包含子程序名称或 CODE 引用变量间接调用函数。

Perl 函数调用和返回值的模型很简单:所有函数都以单个扁平的标量列表作为参数传递,所有函数也以单个扁平的标量列表返回给调用者。这些调用和返回列表中的任何数组或哈希都会折叠,失去其身份——但您始终可以使用按引用传递来避免这种情况。调用和返回列表都可以包含任意数量的标量元素。(通常没有显式返回语句的函数被称为子例程,但从 Perl 的角度来看,它们并没有什么区别。)

在使用签名(参见下面 "Signatures")的子例程中,参数被分配到由签名引入的词法变量中。在 Perl 的当前实现中,它们也可以在 @_ 数组中以与非签名子例程相同的方式访问,但现在不鼓励在使用签名的子例程中以这种方式访问它们。

在不使用签名的子例程中,传递的任何参数都会出现在 @_ 数组中。因此,如果您用两个参数调用一个函数,它们将存储在 $_[0]$_[1] 中。数组 @_ 是一个局部数组,但它的元素是实际标量参数的别名。特别是,如果更新元素 $_[0],则相应的参数也会更新(或者如果它不可更新,则会发生错误)。如果参数是调用函数时不存在的数组或哈希元素,则该元素仅在修改它或获取对它的引用时(以及如果)创建。(Perl 的一些早期版本无论是否将元素分配给它都会创建该元素。)将整个数组 @_ 分配给会移除该别名,并且不会更新任何参数。

在不使用签名的情况下,Perl 不会提供其他方法来创建命名的形式参数。实际上,您所做的只是将它们分配给 my() 列表。未声明为私有的变量是全局变量。有关创建私有变量的详细信息,请参见 "Private Variables via my()""Temporary Values via local()"。要为单独包(可能也是单独文件)中的一组函数创建受保护的环境,请参见 "Packages" in perlmod

return 语句可用于退出子例程,可以选择指定返回值,该返回值将在适当的上下文中(列表、标量或空)进行评估,具体取决于子例程调用的上下文。如果您没有指定返回值,则子例程在列表上下文中返回一个空列表,在标量上下文中返回未定义的值,在空上下文中返回空值。如果您返回一个或多个聚合(数组和哈希),它们将被扁平化在一起,形成一个大的不可区分的列表。

如果未找到return,并且最后一个语句是表达式,则返回其值。如果最后一个语句是循环控制结构,例如foreachwhile,则返回值未定义。空子例程返回空列表。

示例

sub max {
    my $max = shift(@_);
    foreach $foo (@_) {
        $max = $foo if $max < $foo;
    }
    return $max;
}
$bestday = max($mon,$tue,$wed,$thu,$fri);

示例

# get a line, combining continuation lines
#  that start with whitespace

sub get_line {
    $thisline = $lookahead;  # global variables!
    LINE: while (defined($lookahead = <STDIN>)) {
        if ($lookahead =~ /^[ \t]/) {
            $thisline .= $lookahead;
        }
        else {
            last LINE;
        }
    }
    return $thisline;
}

$lookahead = <STDIN>;       # get first line
while (defined($line = get_line())) {
    ...
}

将值分配给私有变量列表以命名参数

sub maybeset {
    my($key, $value) = @_;
    $Foo{$key} = $value unless $Foo{$key};
}

由于赋值复制了值,因此这也具有将按引用调用转换为按值调用的效果。否则,函数可以自由地对@_进行就地修改,并更改其调用者的值。

upcase_in($v1, $v2);  # this changes $v1 and $v2
sub upcase_in {
    for (@_) { tr/a-z/A-Z/ }
}

当然,不允许以这种方式修改常量。如果参数实际上是文字,并且尝试更改它,则会遇到(可能是致命的)异常。例如,这将不起作用

upcase_in("frederick");

如果upcase_in()函数被编写为返回其参数的副本而不是就地更改它们,则会更安全

($v3, $v4) = upcase($v1, $v2);  # this doesn't change $v1 and $v2
sub upcase {
    return unless defined wantarray;  # void context, do nothing
    my @parms = @_;
    for (@parms) { tr/a-z/A-Z/ }
    return wantarray ? @parms : $parms[0];
}

注意,这个(未原型化的)函数并不关心它是传递了真正的标量还是数组。Perl 将所有参数视为@_中一个长而扁平的参数列表。这是 Perl 简单参数传递风格闪耀的一个领域。即使我们向它提供这样的内容,upcase() 函数也能完美地工作,而无需更改upcase() 定义

@newlist   = upcase(@list1, @list2);
@newlist   = upcase( split /:/, $var );

但是,不要尝试这样做

(@x, @y)   = upcase(@list1, @list2);

与扁平化的传入参数列表一样,返回列表在返回时也会被扁平化。因此,您所做的一切只是将所有内容存储在@x中,并使@y为空。有关替代方案,请参阅"按引用传递"

可以使用显式的&前缀调用子例程。在现代 Perl 中,&是可选的,如果子例程已预先声明,则括号也是可选的。当仅命名子例程时,&不是可选的,例如当它用作defined()undef()的参数时。当您想要使用子例程名称或引用进行间接子例程调用时,它也不是可选的,例如使用&$subref()&{$subref}()结构,尽管$subref->()表示法解决了这个问题。有关所有这些内容的更多信息,请参阅perlref

子程序可以递归调用。如果使用 `&` 形式调用子程序,则参数列表是可选的,如果省略,则不会为子程序设置 `@_` 数组:调用时的 `@_` 数组对子程序可见。这是一种效率机制,新用户可能希望避免使用它。

&foo(1,2,3);        # pass three arguments
foo(1,2,3);         # the same

foo();              # pass a null list
&foo();             # the same

&foo;               # foo() get current args, like foo(@_) !!
use strict 'subs';
foo;                # like foo() iff sub foo predeclared, else
                    # a compile-time error
no strict 'subs';
foo;                # like foo() iff sub foo predeclared, else
                    # a literal string "foo"

`&` 形式不仅使参数列表可选,而且还禁用对您提供的参数的任何原型检查。这部分是出于历史原因,部分是为了提供一种方便的方法来作弊,如果您知道自己在做什么。请参阅下面的 "原型"

从 Perl 5.16.0 开始,`__SUB__` 令牌在 `use feature 'current_sub'` 和 `use v5.16` 下可用。它将评估为对当前正在运行的子程序的引用,这允许递归调用而无需知道子程序的名称。

use v5.16;
my $factorial = sub {
    my ($x) = @_;
    return 1 if $x == 1;
    return($x * __SUB__->( $x - 1 ) );
};

`__SUB__` 在正则表达式代码块(例如 `/(?{...})/`)中的行为可能会发生变化。

名称全部为大写的子程序保留给 Perl 核心,同样,名称全部为小写的模块也是如此。全部大写的子程序是一种松散的约定,意味着它将由运行时系统本身间接调用,通常是由于触发了事件。名称以左括号开头的子程序也以相同的方式保留。以下是一些当前执行特殊预定义操作的子程序的列表。

本文档后面有说明

AUTOLOAD

perlmod 中有说明

CLONE, CLONE_SKIP

perlobj 中有说明

DESTROY, DOES

perltie 中有说明

BINMODE, CLEAR, CLOSE, DELETE, DESTROY, EOF, EXISTS, EXTEND, FETCH, FETCHSIZE, FILENO, FIRSTKEY, GETC, NEXTKEY, OPEN, POP, PRINT, PRINTF, PUSH, READ, READLINE, SCALAR, SEEK, SHIFT, SPLICE, STORE, STORESIZE, TELL, TIEARRAY, TIEHANDLE, TIEHASH, TIESCALAR, UNSHIFT, UNTIE, WRITE

PerlIO::via 中有说明

BINMODE, CLEARERR, CLOSE, EOF, ERROR, FDOPEN, FILENO, FILL, FLUSH, OPEN, POPPED, PUSHED, READ, SEEK, SETLINEBUF, SYSOPEN, TELL, UNREAD, UTF8, WRITE

perlfunc 中有说明

import, unimport, INC

UNIVERSAL 中有说明

VERSION

perldebguts 中有说明

DB::DB, DB::sub, DB::lsub, DB::goto, DB::postponed

未公开,在 overload 特性中内部使用

任何以 ( 开头的

BEGIN, UNITCHECK, CHECK, INITEND 子例程与其说是子例程,不如说是命名的特殊代码块,在一个包中可以有多个,而且你不能显式地调用它们。参见 "BEGIN, UNITCHECK, CHECK, INIT and END" in perlmod

签名

Perl 允许使用特殊语法来声明子例程的形式参数,与子例程主体过程代码分离。形式参数列表被称为签名

此功能必须启用才能使用。它会通过在当前作用域中声明 `use v5.36`(或更高版本)自动启用,或者更直接地通过 `use feature 'signatures'` 启用。

签名是子程序主体的一部分。通常,子程序的主体只是一个带括号的代码块,但在使用签名时,签名是一个带括号的列表,它紧接在代码块之前,位于任何名称或属性之后。

例如,

sub foo :lvalue ($x, $y = 1, @z) { .... }

签名声明了对代码块有作用域的词法变量。当调用子程序时,签名首先接管控制权。它从传递的参数列表中填充签名变量。如果参数列表不满足签名的要求,则会抛出异常。当签名处理完成时,控制权将传递给代码块。

位置参数通过在签名中简单地命名标量变量来处理。例如,

sub foo ($left, $right) {
    return $left + $right;
}

接受两个位置参数,这些参数必须在运行时由两个参数填充。默认情况下,参数是必需的,不允许传递超过预期数量的参数。因此,以上等同于

sub foo {
    die "Too many arguments for subroutine" unless @_ <= 2;
    die "Too few arguments for subroutine" unless @_ >= 2;
    my $left = $_[0];
    my $right = $_[1];
    return $left + $right;
}

可以通过省略参数声明中的名称主体,只保留一个裸 `$` 符号来忽略参数。例如,

sub foo ($first, $, $third) {
    return "first=$first, third=$third";
}

虽然忽略的参数不会进入变量,但调用者仍然必须传递它。

通过给出默认值,用 `=` 与参数名称分隔,可以使位置参数可选。

sub foo ($left, $right = 0) {
    return $left + $right;
}

上面的子程序可以用一个或两个参数调用。默认值表达式在调用子程序时求值,因此它可以为不同的调用提供不同的默认值。只有在实际从调用中省略参数时才会对其求值。例如,

my $auto_id = 0;
sub foo ($thing, $id = $auto_id++) {
    print "$thing has ID $id";
}

自动为调用者未提供 ID 的事物分配不同的顺序 ID。默认值表达式也可以引用签名中较早的参数,使一个参数的默认值根据较早的参数而变化。例如,

sub foo ($first_name, $surname, $nickname = $first_name) {
    print "$first_name $surname is known as \"$nickname\"";
}

默认值表达式也可以使用 `//=` 运算符编写,如果调用者省略了值或提供的值为 `undef`,则会对其求值并使用它。

sub foo ($name //= "world") {
    print "Hello, $name";
}

foo(undef);  # will print "Hello, world"

类似地,`||=` 运算符可用于提供默认表达式,该表达式将在调用者提供假值时使用(请记住,缺少或 `undef` 值也是假值)。

sub foo ($x ||= 10) {
    return 5 + $x;
}

可选参数可以像必需参数一样没有名称。例如,

sub foo ($thing, $ = 1) {
    print $thing;
}

即使值不会存储在任何地方,如果相应的参数未提供,仍然会对参数的默认值求值。这是因为对其求值具有重要的副作用。但是,它将在空上下文下求值,因此如果它没有副作用并且不是微不足道的,如果启用了“空”警告类别,它会生成警告。如果无名可选参数的默认值不重要,则可以像省略参数名称一样省略它。

sub foo ($thing, $=) {
    print $thing;
}

可选位置参数必须位于所有必需位置参数之后。(如果没有必需位置参数,则可选位置参数可以是签名中的第一个元素。)如果有多个可选位置参数,并且提供的参数不足以填充所有参数,则将从左到右填充它们。

在位置参数之后,可以使用一个“贪婪”参数来捕获额外的参数。最简单的形式就是一个数组变量。

sub foo ($filter, @inputs) {
    print $filter->($_) foreach @inputs;
}

在函数签名中使用“贪婪”参数,传递的参数数量没有上限。一个“贪婪”数组参数可以像位置参数一样没有名称,在这种情况下,它唯一的用途是取消原本适用的参数数量限制。

sub foo ($thing, @) {
    print $thing;
}

一个“贪婪”参数也可以是一个哈希,在这种情况下,传递给它的参数会被解释为交替的键和值。键和值的个数必须相同:如果参数个数为奇数,则会抛出异常。键会被转换为字符串,如果存在重复的键,则后面的实例会覆盖前面的实例,就像标准的哈希构造一样。

sub foo ($filter, %inputs) {
    print $filter->($_, $inputs{$_}) foreach sort keys %inputs;
}

一个“贪婪”哈希参数可以像其他类型的参数一样没有名称。它仍然要求传递给它的参数数量为偶数,即使它们没有被放入变量中。

sub foo ($thing, %) {
    print $thing;
}

一个“贪婪”参数,无论是数组还是哈希,都必须是函数签名中的最后一个元素。它可以放在强制和可选位置参数之后;它也可以是函数签名中唯一的元素。“贪婪”参数不能有默认值:如果它们没有被传递任何参数,则会得到一个空数组或空哈希。

一个函数签名可以完全为空,在这种情况下,它只检查调用者是否传递了任何参数。

sub foo () {
    return 123;
}

在 Perl 5.36 之前,这些被认为是实验性的,并在 experimental::signatures 类别中发出警告。从 Perl 5.36 开始,这种情况不再发生,尽管该警告类别仍然存在,以向尝试使用类似以下语句禁用它的代码提供向后兼容性。

no warnings 'experimental::signatures';

在当前的 Perl 实现中,当使用函数签名时,参数仍然可以在特殊的数组变量 @_ 中访问。但是,现在不鼓励通过此数组访问它们,并且在新建的代码中不应该依赖它,因为这种能力可能会在将来的版本中发生变化。尝试访问 @_ 数组的代码在编译时会在 experimental::args_array_with_signatures 类别中产生警告。

sub f ($x) {
    # This line emits the warning seen below
    print "Arguments are @_";
}

Use of @_ in join or string with signatured subroutine is
experimental at ...

这两种访问参数的方式之间存在差异:@_ 别名 参数,但函数签名变量获取参数的副本。因此,写入函数签名变量只会改变该变量,并且不会影响调用者的变量,但写入 @_ 的元素会修改调用者用来提供该参数的任何内容。

函数签名和原型(参见 "原型")之间存在潜在的语法歧义,因为它们都以左括号开头,并且都可以在某些相同的位置出现,例如在子程序声明中的名称之后。出于历史原因,当函数签名未启用时,此类上下文中的任何左括号都会触发非常宽松的原型解析。大多数函数签名在这种情况下会被解释为原型,但不会是有效的原型。(有效的原型不能包含任何字母字符。)这会导致一些令人困惑的错误消息。

为了避免歧义,当启用签名时,原型特殊语法将被禁用。不会尝试猜测括号内的组是原型还是签名。在这种情况下,要为子例程提供原型,请使用原型属性。例如,

sub foo :prototype($) { $_[0] }

子例程完全可以同时拥有原型和签名。它们执行不同的任务:原型影响对子例程调用的编译,而签名在运行时将参数值放入词法变量中。因此,您可以编写

sub foo :prototype($$) ($left, $right) {
    return $left + $right;
}

原型属性以及任何其他属性必须出现在签名之前。签名始终紧接在子例程主体块之前。

通过 my() 使用私有变量

概要

my $foo;            # declare $foo lexically local
my (@wid, %get);    # declare list of variables local
my $foo = "flurp";  # declare $foo lexical, and init it
my @oof = @bar;     # declare @oof lexical, and init it
my $x : Foo = $y;   # similar, with an attribute applied

警告:在 my 声明中使用属性列表仍在不断发展。当前的语义和接口可能会发生变化。请参阅属性Attribute::Handlers

my 运算符声明列出的变量在封闭块、条件语句(if/unless/elsif/else)、循环(for/foreach/while/until/continue)、子例程、evaldo/require/use 的文件中是词法范围的。如果列出了多个值,则必须将列表放在括号中。所有列出的元素必须是合法的左值。只有字母数字标识符可以进行词法范围限定——像 $/ 这样的神奇内置变量目前必须使用 local 进行 local 化。

local 运算符创建的动态变量不同,使用 my 声明的词法变量完全隐藏在外部世界,包括任何调用的子例程。如果它是从自身或其他地方调用的同一个子例程,则每个调用都会获得自己的副本。

这并不意味着在静态封闭词法范围内声明的 my 变量将不可见。只有动态范围被切断。例如,下面的 bumpx() 函数可以访问词法变量 $x,因为 mysub 发生在同一个范围内,可能是文件范围。

my $x = 10;
sub bumpx { $x++ }

但是,eval() 可以看到它正在评估的范围内的词法变量,只要这些名称没有被 eval() 本身内的声明隐藏。请参阅perlref

如果需要,可以将 my() 的参数列表赋值,这允许您初始化变量。(如果未为特定变量提供初始化器,则它将使用未定义的值创建。)通常,这用于命名子例程的输入参数。示例

$arg = "fred";          # "global" variable
$n = cube_root(27);
print "$arg thinks the root is $n\n";
# outputs: fred thinks the root is 3

sub cube_root {
    my $arg = shift;  # name doesn't matter
    $arg **= 1/3;
    return $arg;
}

my 只是您可能赋值的修饰符。因此,当您将其参数列表中的变量赋值时,my 不会改变这些变量是作为标量还是数组看待。所以

my ($foo) = <STDIN>;                # WRONG?
my @FOO = <STDIN>;

两者都向右侧提供列表上下文,而

my $foo = <STDIN>;

提供标量上下文。但以下只声明了一个变量

my $foo, $bar = 1;                  # WRONG

这与以下效果相同

my $foo;
$bar = 1;

声明的变量直到当前语句之后才被引入(不可见)。因此,

my $x = $x;

可用于使用旧 $x 的值初始化新的 $x,并且表达式

my $x = 123 and $x == 123

为假,除非旧 $x 恰好具有值 123

控制结构的词法范围并不完全由界定其控制块的大括号限定;控制表达式也是该范围的一部分。因此,在循环中

while (my $line = <>) {
    $line = lc $line;
} continue {
    print $line;
}

$line 的范围从其声明一直延伸到循环构造的其余部分(包括 continue 子句),但不会超出它。类似地,在条件语句中

if ((my $answer = <STDIN>) =~ /^yes$/i) {
    user_agrees();
} elsif ($answer =~ /^no$/i) {
    user_disagrees();
} else {
    chomp $answer;
    die "'$answer' is neither 'yes' nor 'no'";
}

$answer 的范围从其声明一直延伸到该条件语句的其余部分,包括任何 elsifelse 子句,但不会超出它。有关语句修饰符中变量范围的信息,请参阅 "perlsyn 中的简单语句"

foreach 循环默认以 local 的方式动态地对索引变量进行范围限定。但是,如果索引变量以关键字 my 为前缀,或者如果范围内已经存在一个具有相同名称的词法变量,则会创建一个新的词法变量。因此,在循环中

for my $i (1, 2, 3) {
    some_function();
}

$i 的范围延伸到循环结束,但不会超出它,这使得 $i 的值在 some_function() 中不可访问。

一些用户可能希望鼓励使用词法范围变量。为了帮助捕获对包变量的隐式使用(包变量始终是全局变量),如果您说

use strict 'vars';

那么从那里到封闭块结束的任何提到的变量都必须引用词法变量,通过 ouruse vars 预先声明,或者必须使用包名完全限定。否则会导致编译错误。内部块可以使用 no strict 'vars' 来抵消这一点。

my 既有编译时效果,也有运行时效果。在编译时,编译器会注意到它。这主要用于使 use strict 'vars' 静默,但对于生成闭包也是必不可少的,如 perlref 中所述。但是,实际初始化会延迟到运行时,因此它会在适当的时间执行,例如每次循环时。

使用 my 声明的变量不属于任何包,因此永远不会使用包名完全限定。特别地,不允许尝试将包变量(或其他全局变量)设为词法变量

my $pack::var;      # ERROR!  Illegal syntax

事实上,即使词法变量具有相同的名称,动态变量(也称为包变量或全局变量)仍然可以使用完全限定的 :: 符号访问

package main;
local $x = 10;
my    $x = 20;
print "$x and $::x\n";

这将打印出 2010

您可以在文件的最外层范围声明 my 变量,以隐藏任何此类标识符,使其无法被文件外部的世界访问。这在精神上类似于 C 中的静态变量,当它们在文件级别使用时。要使用子例程执行此操作,需要使用闭包(一个访问封闭词法变量的匿名函数)。如果您想创建一个私有子例程,该子例程无法从该块外部调用,它可以声明一个包含匿名子例程引用的词法变量

my $secret_version = '1.001-beta';
my $secret_sub = sub { print $secret_version };
&$secret_sub();

只要该引用从未被模块内的任何函数返回,那么任何外部模块都无法看到该子例程,因为它的名称不在任何包的符号表中。请记住,它并没有真正地被命名为$some_pack::secret_version或其他任何名称;它只是$secret_version,没有限定符,也无法限定。

然而,这对于对象方法不起作用;所有对象方法都必须位于某个包的符号表中才能被找到。有关解决方法,请参阅perlref 中的“函数模板”

持久私有变量

在 Perl 5.10 中,有两种方法可以构建持久私有变量。首先,您可以简单地使用state特性。或者,如果您想保持与 5.10 之前的版本兼容,可以使用闭包。

通过 state() 使用持久变量

从 Perl 5.10.0 开始,您可以使用state关键字来声明变量,而不是my。但是,要使它起作用,您必须事先启用该特性,方法是使用feature pragma,或者在单行程序中使用-E(请参阅feature)。从 Perl 5.16 开始,CORE::state形式不需要feature pragma。

state关键字创建一个词法变量(遵循与my相同的范围规则),该变量从一个子例程调用持续到下一个子例程调用。如果一个状态变量位于一个匿名子例程中,那么该子例程的每个副本都有一个状态变量的副本。但是,状态变量的值仍然会在对同一个匿名子例程副本的调用之间持续存在。(不要忘记sub { ... }每次执行都会创建一个新的子例程。)

例如,以下代码维护一个私有计数器,每次调用gimme_another()函数时都会递增

use feature 'state';
sub gimme_another { state $x; return ++$x }

这个例子使用匿名子例程来创建独立的计数器

use feature 'state';
sub create_counter {
    return sub { state $x; return ++$x }
}

此外,由于$x是词法的,因此任何外部 Perl 代码都无法访问或修改它。

当与变量声明结合使用时,对state变量的简单赋值(如state $x = 42)只在第一次执行。当这些语句在后续时间被评估时,赋值会被忽略。对state声明的赋值行为,其中赋值的左侧包含任何括号,目前是未定义的。

使用闭包的持久变量

仅仅因为一个词法变量在词法上(也称为静态地)作用域到其封闭块、evaldo FILE,并不意味着它在函数内部的工作方式与 C 静态变量相同。它通常更像 C 自动变量,但具有隐式垃圾回收。

与 C 或 C++ 中的局部变量不同,Perl 的词法变量并不一定会在其作用域退出时被回收。如果一些更持久的东西仍然知道该词法变量,它将继续存在。只要其他东西引用一个词法变量,该词法变量就不会被释放——这正是应该的。您不希望内存被释放,直到您完成使用它,或者在您完成使用后仍然保留。自动垃圾回收为您处理了这一点。

这意味着您可以传递或保存对词法变量的引用,而返回指向 C 自动变量的指针是一个严重错误。它还为我们提供了一种模拟 C 函数静态变量的方法。以下是一种为函数提供具有词法作用域和静态生命周期的私有变量的机制。如果您确实想要创建类似于 C 的静态变量,只需将整个函数包含在一个额外的块中,并将静态变量放在函数外部但位于块内。

{
    my $secret_val = 0;
    sub gimme_another {
        return ++$secret_val;
    }
}
# $secret_val now becomes unreachable by the outside
# world, but retains its value between calls to gimme_another

如果此函数是从单独的文件中通过 `require` 或 `use` 引入的,那么这可能没问题。如果它全部位于主程序中,您需要安排 `my` 尽早执行,要么将整个块放在主程序之上,要么更可能地,只在它周围放置一个 `BEGIN` 代码块,以确保它在程序开始运行之前执行。

BEGIN {
    my $secret_val = 0;
    sub gimme_another {
        return ++$secret_val;
    }
}

有关特殊触发代码块 `BEGIN`、`UNITCHECK`、`CHECK`、`INIT` 和 `END` 的信息,请参阅 "perlmod 中的 BEGIN、UNITCHECK、CHECK、INIT 和 END"

如果在最外层作用域(文件作用域)声明,那么词法变量的行为类似于 C 的文件静态变量。它们对同一文件中声明在其下面的所有函数可用,但无法从该文件外部访问。这种策略有时用于模块中创建整个模块可见的私有变量。

通过 local() 创建临时值

警告:通常,您应该使用 `my` 而不是 `local`,因为它更快更安全。例外情况包括全局标点符号变量、全局文件句柄和格式,以及对 Perl 符号表本身的直接操作。`local` 主要用于当前变量的值必须对调用的子例程可见的情况。

概要

# localization of values

local $foo;                # make $foo dynamically local
local (@wid, %get);        # make list of variables local
local $foo = "flurp";      # make $foo dynamic, and init it
local @oof = @bar;        # make @oof dynamic, and init it

local $hash{key} = "val";  # sets a local value for this hash entry
delete local $hash{key};   # delete this entry for the current block
local ($cond ? $v1 : $v2); # several types of lvalues support
                           # localization

# localization of symbols

local *FH;                 # localize $FH, @FH, %FH, &FH  ...
local *merlyn = *randal;   # now $merlyn is really $randal, plus
                           #     @merlyn is really @randal, etc
local *merlyn = 'randal';  # SAME THING: promote 'randal' to *randal
local *merlyn = \$randal;  # just alias $merlyn, not @merlyn etc

`local` 会修改其列出的变量,使其对包含的块、`eval` 或 `do FILE` 以及从该块中调用的任何子例程“局部”。`local` 只是为全局(表示包)变量提供临时值。它不会创建局部变量。这被称为动态作用域。词法作用域使用 `my` 完成,它更类似于 C 的 auto 声明。

某些类型的左值也可以局部化:哈希和数组元素和切片、条件语句(前提是它们的结果始终可局部化),以及符号引用。对于简单变量,这会创建新的、动态作用域的值。

如果local中给出了多个变量或表达式,则必须将它们放在括号中。此运算符的工作原理是将这些变量的当前值保存在其参数列表中,并将其保存在隐藏的堆栈中,并在退出块、子例程或 eval 时恢复它们。这意味着调用的子例程也可以引用局部变量,但不能引用全局变量。如果需要,可以将参数列表分配给它,这允许您初始化局部变量。(如果未为特定变量提供初始化程序,则它将使用未定义的值创建。)

因为local是一个运行时运算符,所以它在每次循环时都会执行。因此,在循环之外局部化变量更有效。

关于 local() 的语法说明

local只是对 lvalue 表达式的修饰符。当您将值赋给local化的变量时,local不会改变其列表是作为标量还是数组被查看。所以

local($foo) = <STDIN>;
local @FOO = <STDIN>;

两者都向右侧提供列表上下文,而

local $foo = <STDIN>;

提供标量上下文。

特殊变量的局部化

如果您局部化一个特殊变量,您将为它赋予一个新值,但它的魔法不会消失。这意味着与这个魔法相关的所有副作用仍然适用于局部化的值。

此功能允许像这样的代码工作

# Read the whole contents of FILE in $slurp
{ local $/ = undef; $slurp = <FILE>; }

但是,请注意,这限制了一些值的局部化;例如,以下语句在 Perl 5.10.0 及更高版本中会因错误而终止,因为 $1 变量是魔法的并且是只读的

local $1 = 2;

一个例外是默认标量变量:从 Perl 5.14 开始,local($_) 将始终从 $_ 中剥离所有魔法,以使其能够在子例程中安全地重用 $_。

警告:绑定数组和哈希的局部化目前无法按描述工作。这将在 Perl 的未来版本中修复;在此期间,请避免依赖绑定数组或哈希的任何特定行为的代码(局部化单个元素仍然可以)。有关更多详细信息,请参阅 "perl58delta 中的绑定数组和哈希的局部化已损坏"

全局变量的局部化

结构

local *name;

在当前包中为全局变量 name 创建一个全新的符号表条目。这意味着其全局变量槽中的所有变量($name、@name、%name、&name 以及 name 文件句柄)都会被动态重置。

这意味着,除其他事项外,这些变量最终携带的任何魔法都会在本地丢失。换句话说,使用 local */ 不会对输入记录分隔符的内部值产生任何影响。

复合类型元素的局部化

同样值得花点时间解释一下当你 local 化复合类型(即数组或哈希元素)的成员时会发生什么。在这种情况下,元素是 按名称 local 化的。这意味着当 local() 的作用域结束时,保存的值将被恢复到 local() 中命名的键的哈希元素,或 local() 中命名的索引的数组元素。如果该元素在 local() 生效期间被删除(例如,通过哈希中的 delete() 或数组的 shift()),它将重新出现,可能会扩展数组并用 undef 填充跳过的元素。例如,如果你说

%hash = ( 'This' => 'is', 'a' => 'test' );
@ary  = ( 0..5 );
{
    local($ary[5]) = 6;
    local($hash{'a'}) = 'drill';
    while (my $e = pop(@ary)) {
        print "$e . . .\n";
        last unless $e > 3;
    }
    if (@ary) {
        $hash{'only a'} = 'test';
        delete $hash{'a'};
    }
}
print join(' ', map { "$_ $hash{$_}" } sort keys %hash),".\n";
print "The array has ",scalar(@ary)," elements: ",
    join(', ', map { defined $_ ? $_ : 'undef' } @ary),"\n";

Perl 将打印

6 . . .
4 . . .
3 . . .
This is a test only a test.
The array has 6 elements: 0, 1, 2, undef, undef, 5

local() 对复合类型中不存在的成员的行为可能会在将来发生变化。local() 对使用负索引指定的数组元素的行为尤其令人惊讶,并且很可能发生变化。

复合类型元素的局部化删除

你可以使用 delete local $array[$idx]delete local $hash{key} 结构来删除当前块的复合类型条目并在其结束时恢复它。它们返回局部化之前的数组/哈希值,这意味着它们分别等效于

do {
    my $val = $array[$idx];
    local  $array[$idx];
    delete $array[$idx];
    $val
}

do {
    my $val = $hash{key};
    local  $hash{key};
    delete $hash{key};
    $val
}

只是 local 的作用域限于 do 块。切片也被接受。

my %hash = (
    a => [ 7, 8, 9 ],
    b => 1,
)

{
    my $x = delete local $hash{a};
    # $x is [ 7, 8, 9 ]
    # %hash is (b => 1)

    {
        my @nums = delete local @$x[0, 2]
        # @nums is (7, 9)
        # $x is [ undef, 8 ]

        $x[0] = 999; # will be erased when the scope ends
    }
    # $x is back to [ 7, 8, 9 ]

}
# %hash is back to its original state

此结构从 Perl v5.12 开始支持。

左值子程序

可以从子程序返回可修改的值。为此,你必须声明子程序返回左值。

my $val;
sub canmod : lvalue {
    $val;  # or:  return $val;
}
sub nomod {
    $val;
}

canmod() = 5;   # assigns to $val
nomod()  = 5;   # ERROR

子程序和赋值右侧的标量/列表上下文是根据将子程序调用替换为标量来确定的。例如,考虑

data(2,3) = get_data(3,4);

这里两个子程序都在标量上下文中调用,而在

(data(2,3)) = get_data(3,4);

(data(2),data(3)) = get_data(3,4);

中,所有子程序都在列表上下文中调用。

左值子程序很方便,但你必须记住,当与对象一起使用时,它们可能会违反封装。正常的变异器可以在设置它正在保护的属性之前检查提供的参数,而左值子程序则不能。如果你在存储和检索值时需要任何特殊处理,请考虑使用 CPAN 模块 Sentinel 或类似的东西。

词法子程序

从 Perl 5.18 开始,您可以使用 `my` 或 `state` 声明私有子程序。与状态变量一样,`state` 关键字仅在 `use feature 'state'` 或 `use v5.10` 或更高版本下可用。

在 Perl 5.26 之前,词法子程序被认为是实验性的,并且仅在 `use feature 'lexical_subs'` 编译指示下可用。它们还会产生警告,除非禁用 "experimental::lexical_subs" 警告类别。

这些子程序仅在声明它们的块内可见,并且仅在声明之后可见。

# Include these two lines if your code is intended to run under Perl
# versions earlier than 5.26.
no warnings "experimental::lexical_subs";
use feature 'lexical_subs';

foo();              # calls the package/global subroutine
state sub foo {
    foo();          # also calls the package subroutine
}
foo();              # calls "state" sub
my $ref = \&foo;    # take a reference to "state" sub

my sub bar { ... }
bar();              # calls "my" sub

您不能(直接)编写递归词法子程序。

# WRONG
my sub baz {
    baz();
}

此示例失败是因为 `baz()` 指的是包/全局子程序 `baz`,而不是当前正在定义的词法子程序。

解决方案是使用 __SUB__

my sub baz {
    __SUB__->();    # calls itself
}

可以预先声明词法子程序。`sub foo {...}` 子程序定义语法尊重任何先前的 `my sub;` 或 `state sub;` 声明。但是,使用它来定义递归子程序是一个坏主意。

my sub baz;         # predeclaration
sub baz {           # define the "my" sub
    baz();          # WRONG: calls itself, but leaks memory
}

就像 `my $f; $f = sub { $f->() }` 一样,此示例会泄漏内存。名称 `baz` 是对子程序的引用,而子程序使用名称 `baz`;它们相互保持存活(参见 "perlref 中的循环引用")。

state submy sub

“state” 子程序和 “my” 子程序有什么区别?每次执行进入声明 “my” 子程序的块时,都会创建每个子程序的新副本。“State” 子程序从包含块的一次执行持续到下一次执行。

因此,一般来说,“state” 子程序更快。但是,如果您想创建闭包,则需要 “my” 子程序。

sub whatever {
    my $x = shift;
    my sub inner {
        ... do something with $x ...
    }
    inner();
}

在此示例中,当调用 `whatever` 时,会创建一个新的 `$x`,还会创建一个新的 `inner`,它可以看到新的 `$x`。“state” 子程序只会看到对 `whatever` 的第一次调用中的 `$x`。

our 子程序

与 `our $variable` 一样,`our sub` 创建对同名包子程序的词法别名。

它的两个主要用途是切换回在内部作用域中使用包子程序

sub foo { ... }

sub bar {
    my sub foo { ... }
    {
        # need to use the outer foo here
        our sub foo;
        foo();
    }
}

以及使子程序对同一作用域中的其他包可见。

package MySneakyModule;

our sub do_something { ... }

sub do_something_with_caller {
    package DB;
    () = caller 1;          # sets @DB::args
    do_something(@args);    # uses MySneakyModule::do_something
}

传递符号表条目(类型球)

警告:本节中描述的机制最初是模拟旧版 Perl 中的按引用传递的唯一方法。虽然它在现代版本中仍然可以正常工作,但新的引用机制通常更容易使用。见下文。

有时您不想将数组的值传递给子程序,而是传递它的名称,以便子程序可以修改它的全局副本,而不是使用局部副本。在 Perl 中,您可以通过在名称前添加星号来引用特定名称的所有对象:*foo。这通常被称为“类型全局”,因为前面的星号可以被认为是变量、子程序等所有有趣前缀字符的通配符匹配。

在评估时,类型全局会生成一个标量值,该值表示该名称的所有对象,包括任何文件句柄、格式或子程序。当被赋值时,它会导致提到的名称引用分配给它的任何*值。示例

sub doubleary {
    local(*someary) = @_;
    foreach $elem (@someary) {
        $elem *= 2;
    }
}
doubleary(*foo);
doubleary(*bar);

标量已经通过引用传递,因此您可以通过显式引用$_[0]等来修改标量参数,而无需使用此机制。您可以通过将所有元素作为标量传递来修改数组的所有元素,但您必须使用*机制(或等效的引用机制)来pushpop或更改数组的大小。传递类型全局(或引用)肯定会更快。

即使您不想修改数组,此机制对于在单个 LIST 中传递多个数组也很有用,因为通常 LIST 机制会合并所有数组值,因此您无法提取单个数组。有关类型全局的更多信息,请参阅"perldata 中的类型全局和文件句柄"

何时仍然使用 local()

尽管存在my,但仍然有三个地方local操作符仍然很出色。事实上,在这三个地方,您必须使用local而不是my

  1. 您需要为全局变量提供一个临时值,尤其是 $_。

    全局变量,如@ARGV或标点符号变量,必须使用local()进行local化。此代码块读取/etc/motd,并将其拆分为由等号行分隔的块,这些块被放置在@Fields中。

    {
        local @ARGV = ("/etc/motd");
        local $/ = undef;
        local $_ = <>;
        @Fields = split /^\s*=+\s*$/;
    }

    特别是,在任何将值分配给 $_ 的例程中,local化 $_ 非常重要。注意while条件语句中的隐式赋值。

  2. 您需要创建一个本地文件或目录句柄或一个本地函数。

    需要自己文件句柄的函数必须在完整类型全局上使用local()。这可以用来创建新的符号表条目

    sub ioqueue {
        local  (*READER, *WRITER);    # not my!
        pipe    (READER,  WRITER)     or die "pipe: $!";
        return (*READER, *WRITER);
    }
    ($head, $tail) = ioqueue();

    请参阅 Symbol 模块,了解如何创建匿名符号表条目。

    由于将引用分配给 typeglob 会创建别名,因此可以用来创建实际上是局部函数,或者至少是局部别名。

    {
        local *grow = \&shrink; # only until this block exits
        grow();                # really calls shrink()
        move();                # if move() grow()s, it shrink()s too
    }
    grow();                    # get the real grow() again

    有关通过这种方式按名称操作函数的更多信息,请参阅 "perlref 中的函数模板"

  3. 您希望临时更改数组或哈希中的单个元素。

    您可以对聚合中的单个元素进行 local 化。通常这在动态上完成

    {
        local $SIG{INT} = 'IGNORE';
        funct();                            # uninterruptible
    }
    # interruptibility automatically restored here

    但它也适用于词法声明的聚合。

按引用传递

如果您想将多个数组或哈希传递给函数(或从函数返回它们)并让它们保持完整性,那么您将不得不使用显式按引用传递。在您这样做之前,您需要了解 perlref 中详细介绍的引用。否则,本节可能对您没有意义。

以下是一些简单的示例。首先,让我们将多个数组传递给函数,并让它对所有数组进行 pop 操作,并返回一个包含所有前最后一个元素的新列表

@tailings = popmany ( \@w, \@x, \@y, \@z );

sub popmany {
    my $aref;
    my @retlist;
    foreach $aref ( @_ ) {
        push @retlist, pop @$aref;
    }
    return @retlist;
}

以下是如何编写一个函数,该函数返回一个包含传递给它的所有哈希中出现的键的列表

@common = inter( \%foo, \%bar, \%joe );
sub inter {
    my ($k, $href, %seen); # locals
    foreach $href (@_) {
        while ( $k = each %$href ) {
            $seen{$k}++;
        }
    }
    return grep { $seen{$_} == @_ } keys %seen;
}

到目前为止,我们只使用普通的列表返回机制。如果您想传递或返回哈希,会发生什么?好吧,如果您只使用其中一个,或者您不介意它们连接,那么正常的调用约定是可以的,尽管有点昂贵。

人们在这里遇到麻烦

(@w, @x) = func(@y, @z);
or
(%w, %x) = func(%y, %z);

该语法根本不起作用。它只设置 @w%w 并清除 @x%x。此外,函数没有传递到两个单独的数组或哈希中:它始终在 @_ 中获得一个长列表。

如果您能安排每个人通过引用来处理这个问题,代码会更简洁,尽管看起来不太好。以下是一个函数,它接受两个数组引用作为参数,并按其元素数量的顺序返回两个数组元素

($wref, $xref) = func(\@y, \@z);
print "@$wref has more than @$xref\n";
sub func {
    my ($yref, $zref) = @_;
    if (@$yref > @$zref) {
        return ($yref, $zref);
    } else {
        return ($zref, $yref);
    }
}

事实证明,您实际上也可以这样做

(*w, *x) = func(\@y, \@z);
print "@w has more than @x\n";
sub func {
    local (*y, *z) = @_;
    if (@y > @z) {
        return (\@y, \@z);
    } else {
        return (\@z, \@y);
    }
}

在这里,我们使用 typeglob 来进行符号表别名。不过,这有点微妙,而且如果您使用 my 变量,它也不起作用,因为只有全局变量(即使伪装成 local)在符号表中。

如果您正在传递文件句柄,通常可以使用裸类型全局变量,例如 *STDOUT,但类型全局变量引用也可以。例如

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

$rec = get_rec(\*STDIN);
sub get_rec {
    my $fh = shift;
    return scalar <$fh>;
}

如果您计划生成新的文件句柄,可以这样做。请注意,只需传递回裸 *FH,而不是其引用。

sub openit {
    my $path = shift;
    local *FH;
    return open (FH, $path) ? *FH : undef;
}

原型

Perl 支持一种非常有限的编译时参数检查,使用函数原型。这可以在 PROTO 部分或使用 原型属性 中声明。如果您声明以下任一项

sub mypush (\@@)
sub mypush :prototype(\@@)

那么 mypush() 将以与 push() 完全相同的方式接受参数。

如果启用了子例程签名(参见 "签名"),则较短的 PROTO 语法不可用,因为它会与签名冲突。在这种情况下,原型只能以属性的形式声明。

函数声明必须在编译时可见。原型仅影响对函数的新式调用的解释,其中新式定义为不使用 & 字符。换句话说,如果您像调用内置函数一样调用它,那么它将像内置函数一样运行。如果您像调用旧式子例程一样调用它,那么它将像旧式子例程一样运行。从这个规则自然得出,原型不会影响子例程引用,例如 \&foo,也不会影响间接子例程调用,例如 &{$subref}$subref->()

方法调用也不受原型影响,因为要调用的函数在编译时是不确定的,因为调用的确切代码取决于继承。

由于此功能的目的是主要让您定义像内置函数一样工作的子例程,以下是其他一些函数的原型,这些函数的解析方式与相应的内置函数几乎完全相同。

Declared as             Called as

sub mylink ($$)         mylink $old, $new
sub myvec ($$$)         myvec $var, $offset, 1
sub myindex ($$;$)      myindex &getstring, "substr"
sub mysyswrite ($$$;$)  mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (@)       myreverse $x, $y, $z
sub myjoin ($@)         myjoin ":", $x, $y, $z
sub mypop (\@)          mypop @array
sub mysplice (\@$$@)    mysplice @array, 0, 2, @pushme
sub mykeys (\[%@])      mykeys $hashref->%*
sub myopen (*;$)        myopen HANDLE, $name
sub mypipe (**)         mypipe READHANDLE, WRITEHANDLE
sub mygrep (&@)         mygrep { /foo/ } $x, $y, $z
sub myrand (;$)         myrand 42
sub mytime ()           mytime

任何反斜杠转义的原型字符都代表一个实际参数,该参数必须以该字符开头(可选地在前面加上 myourlocal),除了 $,它将接受任何标量左值表达式,例如 $foo = 7my_function()->[0]。作为 @_ 部分传递的值将是对子例程调用中给出的实际参数的引用,通过将 \ 应用于该参数获得。

您可以使用 \[] 反斜杠组表示法来指定多个允许的参数类型。例如

sub myref (\[$@%&*])

将允许以以下方式调用 myref()

myref $var
myref @array
myref %hash
myref &sub
myref *glob

而 myref() 的第一个参数将是标量、数组、哈希、代码或 glob 的引用。

未转义的原型字符具有特殊含义。任何未转义的 @% 会吞噬所有剩余参数,并强制列表上下文。由 $ 表示的参数强制标量上下文。& 需要一个匿名子例程,如果作为第一个参数传递,则不需要 sub 关键字或后续逗号。

* 允许子例程在该位置接受一个裸字、常量、标量表达式、类型 glob 或对类型 glob 的引用。该值将作为简单标量或(在后两种情况下)作为对类型 glob 的引用提供给子例程。如果您希望始终将此类参数转换为类型 glob 引用,请使用 Symbol::qualify_to_ref(),如下所示

use Symbol 'qualify_to_ref';

sub foo (*) {
    my $fh = qualify_to_ref(shift, caller);
    ...
}

+ 原型是 $ 的特殊替代方案,当给定文字数组或哈希变量时,它将像 \[@%] 一样工作,但在其他情况下,它将强制对参数进行标量上下文。这对于应该接受文字数组或数组引用作为参数的函数很有用

sub mypush (+@) {
    my $aref = shift;
    die "Not an array or arrayref" unless ref $aref eq 'ARRAY';
    push @$aref, @_;
}

使用 + 原型时,您的函数必须检查参数是否为可接受的类型。

分号 (;) 将必填参数与可选参数分隔开。在 @% 之前它是多余的,它们会吞噬所有其他内容。

作为原型中的最后一个字符,或在分号之前,@%,您可以使用 _ 代替 $:如果未提供此参数,则将使用 $_ 代替。

请注意,上面表格中的最后三个示例是如何被解析器特殊处理的。mygrep() 被解析为真正的列表运算符,myrand() 被解析为真正的单目运算符,其单目优先级与 rand() 相同,而 mytime() 实际上没有参数,就像 time() 一样。也就是说,如果你说

mytime +2;

你将得到 mytime() + 2,而不是 mytime(2),这是在没有原型的情况下解析的方式。如果您想强制单目函数具有与列表运算符相同的优先级,请在原型末尾添加 ;

sub mygetprotobynumber($;);
mygetprotobynumber $x > $y; # parsed as mygetprotobynumber($x > $y)

& 的有趣之处在于,只要它位于初始位置,您就可以用它生成新的语法

sub try (&@) {
    my($try,$catch) = @_;
    eval { &$try };
    if ($@) {
        local $_ = $@;
        &$catch;
    }
}
sub catch (&) { $_[0] }

try {
    die "phooey";
} catch {
    /phooey/ and print "unphooey\n";
};

这将打印 "unphooey"。(是的,仍然存在一些与 @_ 的可见性有关的未解决问题。我目前忽略了这个问题。(但请注意,如果我们使 @_ 成为词法作用域,那么这些匿名子例程可以像闭包一样工作...(天哪,这听起来有点像 Lisp 吗?(别管了。))))

以下是 Perl grep 运算符的重新实现。

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

有些人可能更喜欢完整的字母数字原型。字母数字被有意地从原型中排除,目的是为了将来添加命名形式参数。当前机制的主要目标是让模块编写者为模块用户提供更好的诊断信息。Larry 认为这种表示法对 Perl 程序员来说很容易理解,并且不会过多地干扰模块的核心内容,也不会使模块更难阅读。这些噪音被视觉上封装成一个容易吞咽的小药丸。

如果你尝试在原型中使用字母数字序列,你会收到一个可选的警告 - "原型中存在非法字符..."。不幸的是,早期版本的 Perl 允许使用原型,只要它的前缀是一个有效的原型。一旦大多数违规代码被修复,这个警告可能会在未来的 Perl 版本中升级为致命错误。

最好为新函数编写原型,而不是将原型改造到旧函数中。这是因为你必须特别小心地处理列表上下文和标量上下文之间差异的隐式强加。例如,如果你决定一个函数应该只接受一个参数,像这样

sub func ($) {
    my $n = shift;
    print "you gave me $n\n";
}

而有人一直在用数组或表达式返回列表来调用它

func(@foo);
func( $text =~ /\w+/g );

那么你就在他们的参数前面自动添加了一个 scalar,这可能会让人感到意外。以前用来保存一个值的 @foo 不会被传递进来。相反,func() 现在被传递了一个 1;也就是说,@foo 中元素的数量。而 m//g 在标量上下文中被调用,所以它返回一个布尔值结果,而不是一个单词列表,并推进 pos($text)。哎哟!

如果一个子程序同时具有 PROTO 和 BLOCK,则原型不会在 BLOCK 完全定义之前应用。这意味着具有原型的递归函数必须预先声明才能使原型生效,如下所示

sub foo($$);
sub foo($$) {
    foo 1, 2;
}

当然,这一切都非常强大,应该适度使用,以使世界变得更美好。

常量函数

原型为 () 的函数是内联的潜在候选者。如果优化和常量折叠后的结果是常量或没有其他引用的词法作用域标量,那么它将被用于代替没有 & 的函数调用。使用 & 的调用永远不会内联。(有关声明大多数常量的简单方法,请参见 constant。)

以下函数都将被内联

sub pi ()           { 3.14159 }             # Not exact, but close.
sub PI ()           { 4 * atan2 1, 1 }      # As good as it gets,
                                            # and it's inlined, too!
sub ST_DEV ()       { 0 }
sub ST_INO ()       { 1 }

sub FLAG_FOO ()     { 1 << 8 }
sub FLAG_BAR ()     { 1 << 9 }
sub FLAG_MASK ()    { FLAG_FOO | FLAG_BAR }

sub OPT_BAZ ()      { not (0x1B58 & FLAG_MASK) }

sub N () { int(OPT_BAZ) / 3 }

sub FOO_SET () { 1 if FLAG_MASK & FLAG_FOO }
sub FOO_SET2 () { if (FLAG_MASK & FLAG_FOO) { 1 } }

(请注意,最后一个例子在 Perl 5.20 及更早版本中并不总是内联的,这些版本在包含内部作用域的子程序的行为上并不一致。)你可以通过使用显式的 return 来抵消内联

sub baz_val () {
    if (OPT_BAZ) {
        return 23;
    }
    else {
        return 42;
    }
}
sub bonk_val () { return 12345 }

如前所述,您也可以在 BEGIN 时动态声明内联子例程,前提是它们的代码体由一个没有其他引用的词法作用域标量组成。这里只有第一个例子会被内联。

BEGIN {
    my $var = 1;
    no strict 'refs';
    *INLINED = sub () { $var };
}

BEGIN {
    my $var = 1;
    my $ref = \$var;
    no strict 'refs';
    *NOT_INLINED = sub () { $var };
}

这里有一个不太明显的注意事项(参见 [RT #79908]),即如果变量可能被修改会发生什么。例如

BEGIN {
    my $x = 10;
    *FOO = sub () { $x };
    $x++;
}
print FOO(); # printed 10 prior to 5.32.0

从 Perl 5.22 开始,这会发出弃用警告,从 Perl 5.32 开始,这将成为运行时错误。以前,变量会立即内联,并且不再像正常的词法变量那样工作;因此它会打印 10,而不是 11

如果您仍然希望这样的子例程被内联(没有警告),请确保该变量在声明之外的任何可能被修改的上下文中都没有使用。

# Fine, no warning
BEGIN {
    my $x = 54321;
    *INLINED = sub () { $x };
}
# Error
BEGIN {
    my $x;
    $x = 54321;
    *ALSO_INLINED = sub () { $x };
}

Perl 5.22 还引入了实验性的 "const" 属性作为替代方案。(如果您想使用它,请禁用 "experimental::const_attr" 警告。)当应用于匿名子例程时,它会强制在 sub 表达式求值时调用该子例程。返回值会被捕获并转换为一个常量子例程。

my $x = 54321;
*INLINED = sub : const { $x };
$x++;

本例中 INLINED 的返回值始终为 54321,无论之后对 $x 的修改如何。您也可以在子例程中放入任何任意代码,它将立即执行,其返回值将以相同的方式捕获。

如果您真的想要一个具有 () 原型的子例程,它返回一个词法变量,您可以通过添加一个显式的 return 来轻松地强制它不被内联。

BEGIN {
    my $x = 10;
    *FOO = sub () { return $x };
    $x++;
}
print FOO(); # prints 11

判断一个子例程是否被内联的最简单方法是使用 B::Deparse。考虑这个例子,两个子例程都返回 1,一个具有 () 原型,导致它被内联,另一个没有(deparse 输出已截断以提高清晰度)。

$ perl -MO=Deparse -e 'sub ONE { 1 } if (ONE) { print ONE if ONE }'
sub ONE {
    1;
}
if (ONE ) {
    print ONE() if ONE ;
}

$ perl -MO=Deparse -e 'sub ONE () { 1 } if (ONE) { print ONE if ONE }'
sub ONE () { 1 }
do {
    print 1
};

如果您重新定义一个有资格内联的子例程,默认情况下您会收到一个警告。您可以使用此警告来判断某个特定子例程是否被认为是可内联的,因为它与覆盖非内联子例程的警告不同。

$ perl -e 'sub one () {1} sub one () {2}'
Constant subroutine one redefined at -e line 1.
$ perl -we 'sub one {1} sub one {2}'
Subroutine one redefined at -e line 1.

该警告被认为足够严重,不受 -w 开关(或其缺失)的影响,因为先前编译的函数调用仍然会使用函数的旧值。如果您需要能够重新定义子例程,您需要确保它不被内联,要么通过删除 () 原型(这会改变调用语义,所以要注意),要么通过其他方式阻止内联机制,例如,通过添加一个显式的 return,如上所述。

sub not_inlined () { return 23 }

覆盖内置函数

许多内置函数可以被覆盖,但这应该只在偶尔且有充分理由的情况下尝试。通常,这可能是由一个包尝试在非 Unix 系统上模拟缺失的内置功能来完成的。

覆盖只能通过在编译时从模块导入名称来完成——普通的预声明是不够的。但是,use subs 编译指示允许您通过导入语法,实际上预声明子程序,这些名称随后可以覆盖内置名称。

use subs 'chdir', 'chroot', 'chmod', 'chown';
chdir $somewhere;
sub chdir { ... }

为了明确地引用内置形式,请在内置名称之前加上特殊的包限定符 CORE::。例如,CORE::open() 始终引用内置的 open(),即使当前包从其他地方导入了名为 &open() 的其他子程序。即使它看起来像一个普通的函数调用,但它不是:在这种情况下,CORE:: 前缀是 Perl 语法的一部分,并且适用于任何关键字,无论 CORE 包中有什么。获取对它的引用,即 \&CORE::open,只适用于某些关键字。参见 CORE

库模块通常不应该将内置名称(如 openchdir)作为其默认 @EXPORT 列表的一部分导出,因为这些名称可能会偷偷进入其他人的命名空间并意外地改变语义。相反,如果模块将该名称添加到 @EXPORT_OK,那么用户可以显式导入该名称,但不能隐式导入。也就是说,他们可以说

use Module 'open';

它将导入 open 覆盖。但是,如果他们说

use Module;

他们将获得没有覆盖的默认导入。

上述覆盖内置机制被有意地限制在请求导入的包中。当您希望在不考虑命名空间边界的情况下,在任何地方覆盖内置函数时,有时可以使用第二种方法。这是通过将子程序导入特殊的命名空间 CORE::GLOBAL:: 来实现的。以下是一个非常大胆地用理解正则表达式的程序替换 glob 运算符的示例。

package REGlob;
require Exporter;
@ISA = 'Exporter';
@EXPORT_OK = 'glob';

sub import {
    my $pkg = shift;
    return unless @_;
    my $sym = shift;
    my $where = ($sym =~ s/^GLOBAL_// ? 'CORE::GLOBAL' : caller(0));
    $pkg->export($where, $sym, @_);
}

sub glob {
    my $pat = shift;
    my @got;
    if (opendir my $d, '.') {
        @got = grep /$pat/, readdir $d;
        closedir $d;
    }
    return @got;
}
1;

以下是它的使用方法(滥用)

#use REGlob 'GLOBAL_glob';      # override glob() in ALL namespaces
package Foo;
use REGlob 'glob';              # override glob() in Foo:: only
print for <^[a-z_]+\.pm\$>;     # show all pragmatic modules

初始注释显示了一个人为的、甚至危险的示例。通过全局覆盖 glob,您将强制对每个命名空间的 glob 运算符使用新的(和颠覆性的)行为,而无需拥有这些命名空间的模块的完全认知或合作。当然,这应该非常谨慎地进行——如果必须进行的话。

上面的 REGlob 示例没有实现完全覆盖 Perl 的 glob 运算符所需的所有支持。内置的 glob 在出现在标量上下文或列表上下文中的行为不同,但我们的 REGlob 没有。实际上,许多 Perl 内置函数都有这样的上下文敏感行为,这些行为必须由正确编写的覆盖来充分支持。有关完全功能的 glob 覆盖示例,请研究标准库中 File::DosGlob 的实现。

当您覆盖内置函数时,您的替换函数应(如果可能)与内置函数的原生语法保持一致。您可以通过使用合适的原型来实现这一点。要获取可覆盖内置函数的原型,请使用 `prototype` 函数,并传入 `“CORE::builtin_name”` 作为参数(参见 “prototype” in perlfunc)。

但是请注意,某些内置函数的语法无法用原型表示(例如 `system` 或 `chomp`)。如果您覆盖它们,您将无法完全模仿它们的原始语法。

内置函数 `do`、`require` 和 `glob` 也可以被覆盖,但由于特殊的魔法,它们的原始语法得以保留,您无需为它们的替换函数定义原型。(不过,您无法覆盖 `do BLOCK` 语法)。

`require` 具有特殊的额外暗魔法:如果您以 `require Foo::Bar` 的形式调用您的 `require` 替换函数,它实际上将在 `@_` 中接收参数 `“Foo/Bar.pm”`。参见 “require” in perlfunc

并且,正如您从前面的示例中注意到的,如果您覆盖 `glob`,则 `<*>` glob 运算符也会被覆盖。

类似地,覆盖 `readline` 函数也会覆盖等效的 I/O 运算符 `<FILEHANDLE>`。此外,覆盖 `readpipe` 也会覆盖运算符 ```` 和 `qx//`。

最后,某些内置函数(例如 `exists` 或 `grep`)无法被覆盖。

自动加载

如果您调用一个未定义的子程序,您通常会立即收到一个致命错误,抱怨该子程序不存在。(对于用作方法的子程序也是如此,当该方法在类的包的任何基类中都不存在时。)但是,如果在用于定位原始子程序的包或包中定义了 `AUTOLOAD` 子程序,则该 `AUTOLOAD` 子程序将被调用,并传入本来要传递给原始子程序的参数。原始子程序的完全限定名称神奇地出现在与 `AUTOLOAD` 例程相同的包的全局 `$AUTOLOAD` 变量中。该名称不会作为普通参数传递,因为,嗯,好吧,就是这样。(作为例外,对不存在的 `import` 或 `unimport` 方法的调用将被跳过。此外,如果 `AUTOLOAD` 子程序是 XSUB,则还有其他方法可以检索子程序名称。有关详细信息,请参见 “Autoloading with XSUBs” in perlguts。)

许多AUTOLOAD例程使用eval()加载请求的子例程的定义,然后使用一种特殊的goto()形式执行该子例程,该形式会无痕地擦除AUTOLOAD例程的堆栈帧。(例如,请参阅AutoLoader中记录的标准模块的源代码。)但AUTOLOAD例程也可以模拟该例程而不定义它。例如,假设一个未定义的函数应该只用这些参数调用system。你只需要

sub AUTOLOAD {
    our $AUTOLOAD;              # keep 'use strict' happy
    my $program = $AUTOLOAD;
    $program =~ s/.*:://;
    system($program, @_);
}
date();
who();
ls('-l');

事实上,如果你预先声明了要以这种方式调用的函数,你甚至不需要括号

use subs qw(date who ls);
date;
who;
ls '-l';

CPAN 上的 Shell 模块就是一个更完整的例子,它可以将未定义的子例程调用视为对外部程序的调用。

有一些机制可以帮助模块编写者将他们的模块拆分成可自动加载的文件。请参阅AutoLoaderAutoSplit中描述的标准 AutoLoader 模块,SelfLoader中的标准 SelfLoader 模块,以及perlxs中关于将 C 函数添加到 Perl 代码的文档。

子例程属性

子例程声明或定义可能有一系列与之关联的属性。如果存在这样的属性列表,它将在空格或冒号边界处被拆分,并被视为已经看到了use attributes。有关当前支持哪些属性的详细信息,请参阅attributes。与过时的use attrs的限制不同,sub : ATTRLIST语法适用于将属性与预声明相关联,而不仅仅与子例程定义相关联。

属性必须是有效的简单标识符名称(除了“_”字符之外没有任何标点符号)。它们可能附加一个参数列表,该列表只检查其括号('(',')')是否正确嵌套。

有效语法的示例(即使属性未知)

sub fnord (&\%) : switch(10,foo(7,3))  :  expensive;
sub plugh () : Ugly('\(") :Bad;
sub xyzzy : _5x5 { ... }

无效语法的示例

sub fnord : switch(10,foo();    # ()-string not balanced
sub snoid : Ugly('(');          # ()-string not balanced
sub xyzzy : 5x5;                # "5x5" not a valid identifier
sub plugh : Y2::north;          # "Y2::north" not a simple identifier
sub snurt : foo + bar;          # "+" not a colon or space

属性列表作为一系列常量字符串传递给将它们与子例程关联的代码。特别是,上面有效语法的第二个示例在解析和调用的方式上目前看起来像这样

use attributes __PACKAGE__, \&plugh, q[Ugly('\(")], 'Bad';

有关属性列表及其操作的更多详细信息,请参阅attributesAttribute::Handlers

另请参阅

有关引用和闭包的更多信息,请参阅"perlref 中的函数模板"。如果你想了解从 Perl 调用 C 子例程,请参阅perlxs。如果你想了解从 C 调用 Perl 子例程,请参阅perlembed。如果你想了解如何将你的函数捆绑在单独的文件中,请参阅perlmod。如果你想了解你的系统上有哪些标准库模块,请参阅perlmodlib。如果你想了解如何进行对象方法调用,请参阅perlootut