Perl子程序
1. 子程序的基本属性
1.1 子程序的定义
- 要定义子程序,首先要以关键字 sub 开头,再加上子程序名,花括号,花括号中包含的是函数的主体。
- 子程序可以被定义在程序的任意位置;不需要对子程序进行事先声明;
- 子程序是全局的,如果定义了两个重名的子程序,后面的子程序会覆盖前面的那个;
#a simple subroutine
sub marine{
$n += 1;
print "$n time call marine subroutine.\n";
}
1.2 子程序的调用和返回值
- 子程序调用:在任意表达式中,子程序前面的 & 表示调用这个子程序;如 &marine
- 子程序返回值(标量):
- Perl中子程序没有 “有无返回值” 的区别;
- 任何时候子程序都有返回值,但有时这个值并没有什么用处,每次都写返回值return就显得十分麻烦;
- Perl将子程序执行的最后一次运算的 “结果” ,结果不一定是数值计算得到,当做返回值;
- 子程序返回值 (非标量):
- 如果在子程序最后一句调用列表上下文,可以返回列表值;
#non-scalar return value
sub list_from_fred_to_barney{
if ($fred < $barney) {
$fred..$barney;
} else {
reverse $fred..$barney;
}
}
$fred = 11;
$barney = 6;
@c = &list_from_fred_to_barney; #@c is (11 10 9 8 7 6) now
注意1.1中例:marine子程序执行的最后一次运算是print,它的返回值通常是 1,表示成功输出信息,但显然这不是我们想要的值,所以一定要注意子程序中最后执行的语句。
1.3 子程序的参数
- Perl支持参数传入,要传递参数列表到子程序里,只要在子程序调用的后面加上括号内的列表表达式即可;
- 列表传入子程序后,子程序自动将参数存入 @_,该变量在子程序执行期间有效,子程序通过访问这个数组,以判断参数的个数和参数值;
- 传入的参数 > 子程序需要,后面的参数将被忽略;
- 传入的参数 < 子程序需要,缺少的参数值被定为undef;
#列表传入,max比较传入的两个参数,返回值较大的那个
sub max {
if($_[0] > $_[1]) {
$_[0];
} else {
$_[1];
}
}
&max(10,15); # return 15
&max(10,15,27); # return 15
- 实际上,@_是子程序的私有变量。假如已经存在全局变量 @_,会不会出现问题呢?
- 当然不会!
- 子程序使用@_时,如果已经存在全局变量@_,则全局变量的值在调用前会被先存储起来,等待子程序结束后,全局变量@_恢复原来的值;
- 因此,各种子程序可以不加担心的进行嵌套调用,递归调用时同样可以不考虑这个问题;
- 回忆一下:foreach循环保存控制变量的机制类似,变量的值都是perl自动保存和恢复的。
1.3.1 变长参数列表
- 在常见的Perl代码中,经常把更长的列表作为参数传递给子程序,Perl对传入的列表长度不做限制;
- 我们可以在函数中设置检查;
- 其实,我们更希望可以修改子程序,让其可以接受任意长度的列表;
#变长列表传入
sub max {
if(@_ != 2) {
print "WARNING! &max should get exactly two arguments!\n";
}
...
}
#update subroutine max
sub max {
my ($max_so_far) = shift @_;
foreach (@_) {
if($_ > $max_so_far) {
$max_so_far = $_;
}
}
$max_so_far;
}
$maximum = &max(1,2,34,6,7); #$maximum is 34 now
$max_num = &max(@num); #when @num = (), shift @_ 返回undef, foreach 循环不执行,最终返回undef
2. 子程序中的变量
2.1 子程序中的私有变量
- 在默认情况下,perl里面的所有变量都是全局变量,也就是说,在程序里的任何地方都可以访问他们,但是我们可以借助 my 操作符来定义私有变量;
#my lexical variable
sub max{
my ($m,$n);
($m,$n) = @_; #可以合并为 my($m,$n) = @_;
if ($m > $n) { $m; } else { $n; }
}
2.2 用my声明的词法变量
- 用 my 声明的词法变量不仅可以用在子程序中,可以以用在 if,while,foreach 的语句块内;
- 如: foreach (1…0) { my ($square) = $_ * $_; } -> $square的作用域为foreach循环内部;
- 如果变量的定义并未出现在任何语句块中,则这个变量对整个程序原文件都是私有的;
- 这些私有变量的作用范围容易定位,如果出现调用问题,debug速度会提升;
- my操作符不会改变变量赋值时的上下文
- my ($num) = @_; #列表上下文,和 ($num) = @_; 相同, $num 为@_的第一个参数;
- my $num = @_; #标量上下文,和 $num = @_; 相同,$num为@_中的参数个数;
- my操作符不加括号时,只能用来声明单个变量
- my $fred,$barney; #只声明了$fred;
- my ($fred,$barney); #声明了 $fred 和 $barney;
2.3 持久化私有变量
- 持久化私有变量:
- 由关键字 state 声明;
- state声明的变量,可以在函数被多次调用用保存上次的值 (静态变量?);
- 其作用域同样限制在子程序内部,外部不能调用此私有变量;
#update sunroutine in 1.1
sub marine {
state $n = 0;
$n += 1;
print "$n time call marine subroutine.\n";
}
#注意: v5.10中并不支持在列表上下文中初始化数组和哈希表类型的state变量
state @array = qw/a b c/; #ERROR!!!
3. 子程序的中级用法
3.1 use strict 编译指令
- 编译指令,是指提供给编译器的指令,他告诉编译器如何处理接下来的程序代码;
- 如果希望perl严格要求自己的话,可以再最开始加上 use strict;编译指令;
- 有无 use strict的对比:
- 编写语句:$bam = 3; $ban += 1;
- 无:Perl自动创建bam变量,自动创建ban变量,不给警告;
- 有:Perl自动检查,产生了两个只使用一次的全局变量,发出警告;
- 这时候Perl会报错说 ban 变量没有被声明,提示程序员修改;
3.2 return操作符
- 如果希望子程序执行到某个条件就停止的话,可以使用 return 操作符
#return character
sub find_5 {
foreach(@_) {
if($_ == 5) {
print "5 is in this array";
return $_;
}
}
print "5 is in this array";
return 0;
}
$num = &find_5(1..8);
3.3 省略 & 字符
- 如果Perl内置函数和子程序名称冲突,调用子程序时必须使用 & 字符
- 如果Perl能够从语法上判断是子程序调用语句,调用子程序时可以省略 & 字符
- 子程序定义在子程序调用之前;
- 子程序调用时将列表放在圆括号中;如:$num = find_5(1…8);
4. 子程序签名(补充)
- 到目前为止,我们知道子程序内部接受参数时,从@_中获取参数列表,然后复制给私有变量,如&max函数。
- Perl 5.20中添加了子程序签名特性,目前还是实验特性。
- 使用子程序签名之后,就可以把变量声明移到花括号外面,直接写在程序明后的括号中。
#my lexical variable
sub max{
my ($m,$n);
($m,$n) = @_;
if ($m < $n) { $m; } else { $n; }
}
#使用子程序签名
use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);
sub max($m,$n){
if ( $m < $n) { $n; } else { $m; }
}
- 这时,Perl自动声明这两个变量为子程序的私有变量,运行效果基本相同;但之前可以给 &max 传递多个参数,不会报错,但是使用了子程序签名之后就会报错;
- 提示: Too many arguments for subroutine.
- 因此,子程序签名是会帮助检查参数数量的;
- 如果希望可以传递任意长度的数组:
- 在声明变量的时候,声明数组 (数组的大小是不会被限制的);sub max ($max_so_far, @rest) { … };
- 在声明变量的时候,使用 @ 占位,表明参数数量不定; sub max ($max_so_far, @) { … };
- 如果传入参数少于声明参数数量:
- 在声明变量的时候,可以指定默认值; sub max ($fred = 0, $barney = 7) { … };