函数签名
Perl支持函数签名的功能,目前函数签名还是实验阶段的功能,未来可能会发生改变。
要使用函数签名,需要先开启签名特性。且默认会给出该实验特性的警告,可禁用该警告。
use feature 'signatures';
no warnings 'experimental::signatures';
例如:
use feature 'signatures';
no warnings 'experimental::signatures';
sub f($num1, $num2){
return $num1 + $num2;
}
say f(3, 5);
这表示将调用子程序f(3,5)
时传递的两个参数分别保存到标量变量$num1 $num2
中,这两个标量变量是该子程序的词法变量,只在该子程序作用域内可见。
如果调用子程序时传递的参数(数量)和函数签名不一致,将报错。
上面使用函数签名的子程序,其功能等价于下面手动处理参数的子程序:
sub f{
die "arguments too long" if @_ > 2;
die "arguments too short" if @_ < 2;
my $num1 = $_[0];
my $num2 = $_[1];
return $num1 + $num2;
}
最简单的函数签名是没有任何参数的签名。如下(注意小括号不能省略):
use feature 'signatures';
no warnings 'experimental::signatures';
sub f(){}
这表示调用子程序f时,不能传递任何参数。
函数签名和原型
Perl函数签名和子程序的原型类似,都可以在一定程度上检测参数,且定义子程序时都使用小括号。
为了避免产生歧义,在开启了use feature 'signatures'
时可明确使用:prototype
属性来声明原型部分。并且,函数签名和原型可以同时存在。例如:
use feature 'signatures';
no warnings 'experimental::signatures';
# 开启了`use feature 'signatures';`后,
# 使用prototype属性定义原型
sub foo :prototype($) {
return $_[@];
}
# 下面的子程序定义中出现两个小括号是合法的
sub bar :prototype($$) ($left, $right) {
return $left + $right;
}
函数签名和原型有很大不同:
- 原型是在编译期间,对子程序调用传递的参数进行检查
- 函数签名是在运行期间,像其他编程语言一样处理函数参数,而不再需要手动操作
@_
来管理参数。它在一定程度上也能检测参数
这里需要注意的是,和其他语言不一样,Perl的函数签名是在运行期间(子程序被调用准备开始执行时)生效,而不是在编译期间生效。
函数签名和@_
虽然使用函数签名时,perl会自动将传递的参数赋值给签名中指定的变量。但函数签名和@_
不冲突,使用函数签名时,仍然可以去操作@_
。
实际上,函数签名处理的参数和@_
中保存的参数是有区别的:
- 函数签名的设置保存的变量是参数的拷贝,函数内修改参数变量不会影响外部数据
@_
中保存的参数是外部数据的别名引用,函数内修改@_
内的元素会影响外部数据
因此,函数签名设置的参数变量,等价于手动将@_
内的元素的值赋值给了函数内的私有变量。
例如:
use feature 'signatures';
no warnings 'experimental::signatures';
sub ff($a, $b){
$a++;
$_[1]++;
say "in routine: $a, $_[1]";
}
my ($x, $y)=(33, 44);
ff($x, $y);
say "outer data: $x, $y";
输出结果:
in routine: 34, 45
outer data: 33, 45
函数签名的用法细节
定义子程序时,函数签名的小括号总是书写在代码体大括号的前面。函数前面的前面可能是子程序名称、原型或子程序属性定义。总之,函数前面之后必须紧跟着大括号代码体。例如:
sub f($left, $right){
return $left+$right;
}
当调用带有函数签名的子程序时,总是先处理签名部分:将调用子程序时传递的参数赋值给签名中指定的参数变量。如果参数处理成功,则开始进入执行子程序的代码体,如果签名参数处理失败(比如参数数量不合理),将报错。
位置参数
函数签名中,使用$
开头的标量变量是位置参数。调用子程序传递参数时,会根据签名中的参数书写位置决定如何赋值。
例如,下面的函数签名表示接收两个参数,分别赋值给私有的标量变量$left $right
。
sub f($left, $right){
return $left+$right;
}
位置参数是强制的,函数签名中有几个位置参数(形参),调用子程序时就必须传递几个实参数据。
如果想忽略传递的某个参数,使用不带变量名的$
符号即可:
sub ff($first, $, $third){
say "$first, $third";
}
虽然ff()的第二个参数被忽略,但调用ff子程序时,也必须传递该位置参数。也即调用ff()时,必须要有三个参数。
可选参数:默认值参数
可以为位置参数设置默认值,带有默认值的参数也被称为非必须的可选参数:
sub ff($num1, $num2=10){
return $num1 + $num2;
}
此时调用ff子程序可以传递两个参数,也可以只传递一个参数,该参数将被分配给$num1
,同时$num2
采用默认值10。
注意,参数默认值是在调用子程序时处理参数的运行期间进行评估计算的,因此,某个参数的默认值可以是它前面的参数变量。另一方面,参数默认值只在缺失该参数时才评估。例如:
# 如果只传递一个参数,则默认$num2等价于$num1
sub ff($num1, $num2=$num1){
return $num1 + $num2;
}
# 如果传递两个参数,则不进行自增操作
my $auto_id = 0;
sub fff($name, $id=$auto_id++){
return "$name, $id";
}
可选参数(即默认值参数)必须放在位置参数的后面:
sub f($one, $two, $three=3){}
# 错
sub f($one, $two=2, $three){}
可使用多个默认值参数,此时将根据位置对应分配:
sub f($one, $two, $three=3, $four=4){}
f(11,22,33); # $three=33,$four=4
数组或hash:吸收多个参数
函数签名中可使用@
前缀的参数变量,表示定义一个私有的数组变量,并吸收剩余所有参数,将这些参数保存到该数组变量中。
sub f($one, $two, @others){}
f(1,2,3,4,5,6);
#=> @others=(3,4,5,6,7)
上面的子程序中,如果只传递两个参数,则@others
数组为空数组。
函数签名中的@
也可以不跟名称,这样可以取消调用子程序时的参数数量,同时又无需处理多余参数:
# 可以传递两个或两个以上任意多的参数
sub f($one, $two, @){}
还可以使用%
前缀的参数变量,表示定义一个私有的hash变量,并吸收剩余所有参数,将这些参数成对地保存到hash变量中。如果吸收的参数数量是奇数,则报错。将剩余参数保存为hash时,所有作为key的参数都将被转换为字符串,且如果key相同,则后出现的覆盖先出现的键值对。
例如:
sub f($one, %persons){}
同样,函数签名中的%
可以不带名称,此时它可以用来检测它所吞掉的剩余参数数量是否是偶数个:
sub f($one, %){}
由于@
和%
会吞掉剩余所有参数,因此在函数签名中,它们必须放在签名的最后面。