原型子程序和函数签名
通常情况下,使用如下方式定义子程序:
sub subname{
BODY
}
这种方式定义的子程序参数是很自由的,可以随意传递参数到子程序,它们都会保存到@_
,参数相关的处理需要操作@_
,也即在运行时处理参数。
Perl还支持定义原型子程序,定义原型子程序要求在子程序名称后加上括号,括号内可能包含一些特殊的用于表示原型的元字符。
例如:
sub proto_sub($){
say "@_";
}
该原型子程序要求调用proto_sub时,需要传递一个且必须是一个标量参数(严格来说是进入标量上下文,会转换为标量数据)。
定义原型子程序后,在编译期间,会检查所有调用原型子程序的代码,检查调用原型子程序时传递的参数是否符合原型定义。如果不符合,将编译错误(编译错误而非运行时错误)。
因此,在一定程度上,原型可以作为限制子程序的参数类型、参数数量的一种手段。它有点类似于函数签名,但不同于函数签名。在后面,将介绍Perl中支持的函数签名功能。
需要注意,以sigil前缀&
调用子程序时会跳过编译期间的原型检测。因此,如非必要,否则不要使用&subname
的方式调用原型子程序。
原型字符
Perl的原型由几种具有特殊意义的字符来表示。例如,下面是官方手册中(perldoc perlsub
)给出的原型子程序定义示例:
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 $a, $b, $c
sub myjoin ($@) myjoin ":", $a, $b, $c
sub mypop (+) mypop @array
sub mysplice (+$$@) mysplice @array, 0, 2, @pushme
sub mykeys (+) mykeys %{$hashref}
sub mygrep (&@) mygrep { /foo/ } $a, $b, $c
sub myrand (;$) myrand 42
其中:
-
$
表示该参数处于一个标量上下文中因此,
sub mylink($$)
表示要传递两个参数,这两个参数都会转换为标量数据 -
@
和%
都表示吞掉剩余所有参数,并进入列表上下文 -
\$
、\@
和\%
分别表示对应的参数必须以$
、@
和%
开头,即必须传递标量变量(而不是字面量标量)、数组变量和hash变量\$
更特殊一些,它除了可以传递$
标量变量,还可以传递任意标量左值,例如$foo = 7
或f()->[0]
- 实际上,
\X
表示该参数必须以X开头,支持的反斜线原型包括\$ \@ \% \& \*
# 必须传递两个参数,第一个参数会被转换为标量数据 # 第二个参数必须是'@xxx'格式的数组变量 sub f($\@){}
-
可使用
\[]
对反斜线原型进行归纳。例如\[$@%]
表示该参数可以传递$xx
、@xx
或%xx
的任意一种sub f(\[%@$&]){} # 允许的调用方式: f %x; f @x; f $x; f &x;
-
;
用于分隔强制参数和可选参数,分号左边的参数不可省略,右边的参数可省略。显然,@
或%
前使用;
没有意义,因为它们表示的列表本身就允许是空列表# 必须传递2个或3个参数,第三个参数可选 sub f($$;$){}
-
+
表示该参数可以是数组变量或数组引用,或者是hash变量或hash引用-
无论该参数传递的是数组变量还是数组引用,都会自动转换为数组引用,同理hash变量也会转换为hash引用
-
使用该原型时,有必要在代码体中检测引用的类型是数组引用还是hash引用
sub f(+){say "@_"} my @arr=(1,2,3); f @arr; # ARRAY(0x557c8cb6ba38) f \@arr; # ARRAY(0x557c8cb6ba38)
-
-
&
表示该参数是一个匿名子程序或子程序引用,当它作为第一个参数时,可省略sub关键字以及该参数后的逗号分隔符。这是一个非常有趣的语法,甚至它可以实现新的Perl语法,比如可以实现类似于grep {} @arr
的语句块用法,在perldoc persub
中也给出了通过该功能实现try{}catch{}
语法# 类似于map的功能 sub f(&@){ my $sub_ref = shift; my @arr=(); for (@_){ push @arr, $sub_ref->($_); } return @arr; } my @arr = f { $_ * 2 } 11,22,33; say "@arr";