子程序引用和匿名子程序

子程序也有引用,创建子程序引用的方式为\&subname

例如:

my $sub_ref = \&subname;

通过子程序引用调用子程序的方式包括:

  • (1).&$sub_ref([ARGS])或完整格式的&{$sub_ref}([ARGS]),某些时候必须使用完整格式
  • (2).$sub_ref->([ARGS])
  • (3).&$sub_ref或完整格式的&{$sub_ref},某些时候必须使用完整格式

注意,使用&前缀的副作用,它会跳过原型检测,且不手动传递参数时,将会把当前所在环境的@_作为参数传递给子程序。

例如:

sub subname{
  say "@_";
}

my $sub_ref = \&subname;
&$sub_ref(1,2,3);
$sub_ref->(11,22,33);

sub f{
  &$sub_ref;
}

f(111,222,333);

输出结果:

1 2 3
11 22 33
111 222 333

由于子程序的引用是一个标量变量,因此子程序的引用可以保存到变量、保存到数组或hash。

例如:

sub subname{say "@_"}
my $sub_ref = \&subname;

# 子程序引用保存到数组
my @arr = ($sub_ref);
$arr[0]->(1,2,3);  # 调用子程序
&{$arr[0]}(1,2,3); # 必须使用完整格式,不能使用&$arr[0](1,2,3)

# 子程序引用保存到hash
my %h = (
  sub1 => $sub_ref,
);
$h{sub1}->(1,2,3);
&{$h{sub1}}(1,2,3); # 必须使用完整格式

不仅可以将子程序引用保存起来,还可以将子程序引用作为参数传递给函数、作为函数返回值返回。

例如:

sub subname{say "@_"}
my $sub_ref = \&subname;

# 子程序引用作为参数传递给函数
# 第一个参数是子程序引用,剩余参数传递给该子程序
sub call_sub_ref{
  my $sub_ref = shift;
  $sub_ref->(@_);  # 或者直接 &$sub_ref;
} 
call_sub_ref($sub_ref, 11, 22, 33);

# 子程序引用作为函数返回值
sub return_sub_ref{
  sub inner_sub{ say "@_"; }
  return \&inner_sub;
}
my $sub = return_sub_ref;
$sub->(111,222,333);

匿名子程序

和匿名数组、匿名hash类似,匿名子程序也是引用。

# 创建匿名子程序的方式
sub {
  ...
}

一般情况下,会将匿名子程序赋值给某个变量,那么这个变量就是一个子程序引用。

my $sub = sub {
  say "@_";
}

$sub->(1,2,3);

也可以将匿名子程序放进数组、hash:

# 匿名子程序放进hash
my %h = (
  sub1 => sub { say "sub1: @_"; },
  sub2 => sub { say "sub2: @_"; },
);

$h{sub2}->(11,22,33);

还可以将匿名子程序作为参数传递给子程序,或者当作子程序返回值被返回:

# 作为参数被传递
sub subname {
  my $sub = shift;
  $sub->(11,22,33);
}
subname(sub{say "@_";});

# 返回匿名子程序
sub subname {
  return sub {
    say "@_";
  }
}
my $sub = subname();
$sub->(11,22,33);

Perl子程序的引用计数

Perl中的子程序和标量、数组等类似,它也是保存在内存中实实在在的数据。这和其他语言可能有所区别。

实际上,Perl子程序也有引用计数值:

sub sub1{say "hello";}  # 内存中子程序的引用计数为1
my $s = \&sub1;         # 内存中子程序的引用计数为2
{
  my $ss = $s;          # 内存中子程序的引用计数为3
}  # 内存中子程序的引用计数为2

当子程序的引用计数值减为0,该子程序将被销毁。

例如:

{
  my $sub = sub { say "hello"; }
}
$sub->();   # 报错

当离开大括号语句块时,my声明的私有变量$sub被销毁,使得匿名子程序的引用计数减为0,它被销毁。

需要注意的是,具名子程序的名称是非私有的变量名称,它相当于没有使用my声明的全局子程序名称:

{
  sub subname1{say "hello";}
}
subname1();  # 不报错

sub sub1{
  sub subname2{say "hello";}
}
subname2();  # 不报错

即使离开了作用域,subname1和subname2子程序仍然有效。