定义子程序
Perl中有许多内置的系统函数,如chomp、sort、print等,每一个这样的函数都可以实现各种不同的操作。Perl也可以让用户根据需要自己创建子程序,来实现各种特定操作。一个完整的子程序同Perl的内置函数或操作符一样,由子程序名称以及子程序的代码所组成。子程序名属于Perl标识符范畴,并拥有独立的名字空间。子程序名应尽量不与Perl中自带的操作符或函数同名,否则易引起混淆和错误。
定义子程序,即使用代码说明这个子程序所要实现的操作。原则上,子程序可以被定义在程序中的任意位置。要定义一个子程序,使用关键字sub加上子程序名,后接用花括号围住的代码块即可。例:
sub printy{print'Y'} #定义一个名为“printy”的子程序,其功能为输出Y
sub sum{$n+$m} #定义一个子程序,其功能为计算$n与$m的和
调用子程序
当一个子程序在代码中的任何位置被定义后,就可以在程序中被调用。使用与号“&”加上子程序名即可调用一个子程序。例:
&printy; #调用printy子程序
与号是可以省略的,但必须满足两个条件:1.子程序在先前的代码中已被定义。2.子程序名不能与Perl内置函数重名。如果能满足上述的两个条件,在调用子程序时就可以省略与号。例:
printy; #省略&的调用
返回值
Perl中,所有的子程序都有一个返回值,即不存在无返回值的子程序,但并不是所有的子程序都包含有用的返回值。有的子程序用于计算,其返回值就可以为计算结果,而有的子程序的功能类似于操作符,其用来实现一些特定操作,这样的子程序的返回值往往就是无用的。
子程序中最后一个被计算的表达式的返回值就是该子程序的返回值。最常见的作为返回值的代码是自成一行的一个表达式。如一个运算式,或自成一行的一个变量,运算的结果或变量的当前值会作为返回值返回。例:
sub sum{$n+$m} #返回值是加法运算的结果
作为返回值的代码也可以是一个赋值式。实际上,一个赋值式做了两项工作,即先对右边的表达式进行运算,并将运算结果返回,然后再将这个返回的结果赋值给左边的内容。所以一个赋值式的返回值就是其右边表达式的运算结果。例:
sub sum{$_=$n+$m} #返回值就是右边加法运算的结果
子程序的返回值不一定是一个标量,其也可以是一个数组或列表。例:
sub list{"@n"} #子程序的返回值为一个内插的数组
参数
在子程序中不一定要使用存在于整个程序中的全局变量,这会造成很多的不便。而事实上,使用子程序参数才是子程序的实际用法。
子程序的参数为一个参数列表,这个列表书写于调用子程序语句中子程序名之后。在子程序被调用时,Perl会自动将参数列表更名为特殊的数组@_,该数组变量仅在子程序运行期间有效。即类似于foreach循环的控制变量,在子程序被调用前,如果@_已有定义,那么@_会被存起来,并在子程序结束时恢复原来的值。这样的性质保证了子程序递归等高级用法的实现,这里不详细论述。
@_中的元素可供子程序执行期间调用。为方便起见,建议在子程序的开头将@_中的元素赋值到新的自定义变量中以便于子程序代码的书写和阅读。这对于某些复杂的子程序尤为重要。
sub max{
($n,$m)=@_; #将@_中的元素赋值给变量列表
if($n>$m){$n}else{$m}
}
print max($a,$b); #使用参数列表的子程序调用,($a,$b)会被自动化名为@_
子程序对参数列表长度的自适应
在真实的Perl代码中,参数列表的长度往往是未知的,即是任意长度的。这就要求子程序能够对任意长度的参数列表实现自适应,而不仅仅只适用于固定长度的参数列表。自适应的实现可以有各种灵活的方法,下文仅以高水线算法子程序为例,说明这个技巧:
sub max{
($max,@n)=@_;
for(@n){$max=$_if$_>$max}
$max;
}
上例中,子程序首先将传入参数赋值给两个部分:参数列表的第一个元素赋值给一个作为临时最大值的变量,余下部分赋值给一个数组。然后对这个数组中的每一个元素进行循环迭代,只要数组中的元素大于当前的临时最大值,就把这个元素的值赋值给临时最大值变量。这样经过全部的迭代运算后,临时最大值变量所存储的值就是参数列表中的最大值。这个算法被称为“高水线算法”。
上例中,使用foreach循环结构实现了对参数列表长度的自适应,即无论参数列表的长度是多少,foreach结构都能对其进行循环迭代,包括只有一个参数的情况和没有参数的情况。
return操作符
类似于循环控制的last操作符,return操作符可以立即终止子程序并可以返回一个返回值。返回值即return的参数,该参数也可省略,从而只结束子程序而不返回任何值。例:
sub one{
for(0..$#_){return$_if$_[$_]==1}
-1;
}
上例中,使用foreach循环对参数列表的索引值列表0..$#_进行循环迭代,并取出@_中索引值对应的元素$_[$_]与1比较,只要该元素与1相等,就执行return,返回这个索引值并立即结束子程序。如果进行完了全部的循环迭代仍未执行过return,则将-1作为返回值返回。
私有变量
默认情况下,Perl中的所有变量都是全局变量,即这个变量在程序中的任何位置都可以访问,且访问到的都是同一个变量。到目前为止,所有例程序中使用的变量也都是全局变量。子程序中的代码是一个相对封闭且独立于外界的代码块。所以如果将其中的变量设定为子程序的私有变量,从而不受外界代码的影响,这将大大提高子程序的稳定性与代码的易维护性。
Perl中,可以使用my操作符来创建私有变量,又称为词法变量或有限作用域变量。被创建的私有变量不受外界同名变量的影响,反之亦然,即在代码块中被创建的私有变量$n与外界的$n互不相干,这是完全无关的两个变量。my操作符可以一次性声明多个词法变量,将多个需要声明的词法变量一起放入my后面的括号中,每个参数之间以逗号隔开即可。my声明语句不一定要自成一行,也可以与赋值式等连用。
词法变量可以被创建于程序的任何位置,包括直接创建于没有任何语句块的程序代码中。词法变量的作用域限定于定义它的最内层语句块或文件,即被创建的词法变量对于其所属的最内层语句块或文件来说是私有的,这个私有的词法变量与外界的重名变量互不相干。例:
my($i,$j,@a)=@_; #一次性声明三个词法变量并赋值
使用my操作符声明词法变量又可以理解为新建一个或多个变量,因为是新建,所以每一次声明词法变量时,被声明的变量都是新的,标量变量会被创建为undef,数组和哈希会被创建为空数组或空哈希。
my操作符的作用仅为声明词法变量,并不会改变被声明的词法变量所处的上下文。即在判断上下文时,完全可以忽略my这个词。例:
my$n=@n; #变量上下文
my($m)=@n; #列表上下文
由于词法变量具有使程序更为稳定,通用性更强且易于维护等特点,所以建议所有的变量在使用前均进行声明。
持久性私有变量
state操作符与my类似,是另一种用来声明词法变量的操作符,其可以声明持久性私有变量。使用state声明变量时,被声明的变量只会在第一次声明时进行初始化,然后state所在的声明语句就会被Perl忽略,不会再次声明并初始化这个变量。这样的操作就不仅可以声明变量私有,还可以在多次调用该变量期间保留变量值。这种只声明一次而具有持久性的变量就称为持久性私有变量。state操作符是Perl 5.10的新功能,所以使用时要启用5.10版本。例:
use 5.010; #启用Perl 5.10
sub plus{state$n++} #声明$n为持久性私有变量
plus;plus;
print plus; #三次调用子程序,并输出最终返回值
如上例所见,$n在三次调用子程序期间没有被初始化,而是保留了每一次运算的结果。上例中的双目赋值操作符也可以用前置自增代替:
sub plus{++state$n} #声明一个持久性私有变量,然后对其进行前置自增
由于Perl语言自身的限制,在使用state声明列表、数组或哈希类型的变量时,不能同时进行声明和赋值操作,只能分开书写,否则程序会报错。在最新版本的Perl中,仍然无法使用这种写法。例:
use 5.20.2; #启用目前最新的Perl语言版本
state($n,$m)=@_;
state@n=qw/a b c/; #不能使用这两种写法。即不能同时声明并赋值
可以将声明语句与赋值语句分开书写,例:
use 5.20.2;
state($n,$m,@i); #先单独声明持久性私有变量
($n,$m)=@_;
@i=qw/a b c/; #声明与赋值分开操作
strict编译指令
Perl是一门在语法及编程上限制较少的语言。但如果希望Perl能够在语法上更加严格,遵循一些优良的代码风格时,就可以启用strict编译指令。从Perl 5.12开始,strict编译指令将被自动启用,所以如果启用了5.12以上的版本,就相当于隐式打开strict编译指令。例:
use strict; #启用strict编译指令
use 5.20.2; #隐式启用strict编译指令
启用strict编译指令最显著的特点,就是会强制要求声明每一个新变量,如果没有声明,则程序不会运行,并且会发出警告。例:
use strict;
$n=1; #由于启用了strict编译指令且此处没有对变量进行声明,所以程序不会运行并会发出警告
而正确的写法为:
my$n=1; #声明这个新变量
strict的这种限制仅适用于程序中的新变量,对于Perl的内置变量,如默认变量$_,用于子程序参数的数组变量@_等,均无需声明。另外,$a与$b也属于Perl的内置变量,它们是sort函数的内置变量(详见“高级排序”),所以这两个变量也不在strict编译指令的约束范围内,且在实际的编程中,也不建议将这两个变量当作普通变量使用。
樱雨楼
完成于2016.1.12
最后修改于2016.1.30