Per的子程序(subroutine)
Per的子程序(subroutine)
- perl中的子程序其实就是自定义函数。它使用
sub
关键字开头,表示声明一个子程序 - 子程序名称有独立的名称空间,不会和其它名称冲突
- Perl中的子程序中可以定义、引用、修改全局变量,这和几乎所有的语言都不同。当然,也可以定义局部变量
- perl中使用
&SUB_NAME()
的方式调用SUB_NAME子程序,&
有时候可以省略,括号有时候也可以省略。具体的规则见后面
1 | sub mysub { |
- perl的子程序最后一个执行的语句的结果或返回值就是子程序的返回值(是最后一个执行的语句,不是最后一行语句)。所以子程序总会有返回值,但返回值并不总是有用的返回值
1
2
3
4
5
6sub mysub {
$n += 2;
print "hello world\n";
$n + 3;
}
$num = ⊂
这里的&sub
有返回值,它的返回值是最后执行的语句$n+3
,并将返回值赋值给$num
,所以$num
的值为5。
如果把上面的print作为最后一句:
1 | sub mysub { |
上面的print才是子程序的返回值,这个返回值是什么?这是一个输出语句,它的返回值为1,表示成功打印。这个返回值显然没什么用。所以,子程序最后执行的语句一定要小心检查。
那上面的$n+3
的结果是什么?它的上下文是void context,相加的结果会被丢弃。
子程序还可以返回列表。
1 | ($m,$n)=(3,5); |
- 如果想在子程序的中间退出子程序,就可以使用
return
子句来返回值。当然,如果这个返回语句是最后一行,就可以省略return1
2
3
4
5
6
7
8
9
10# 返回一个偶数
$n=20;
sub mysub {
if ($n % 2 == 0){
return $n;
}
$n-1; # 等价于 return $n-1;
}
$oushu=&mysub;
print $oushu,"\n";
子程序参数
Perl子程序除了可以直接操作全局变量,还可以传递参数。例如:
1 | &mysub(arg1,arg2...); |
至于什么时候可以省略括号,后面再解释。
- 调用子程序时传递的参数会传入子程序,它们存储在一个特殊的数组变量
@_
中 - 既然
@_
是数组,就可以使用$_[index]
的方式引用数组中的各元素,也就是子程序的各个参数 @_
数组只在每个子程序执行期间有效,每个子程序的@_
都互不影响。子程序执行完成,@_
将复原为原来的值
1 | # 返回最大值 |
如果上面的子程序给的参数不是两个,而是3个或者1个(&mysub(10,20,30);
、&mysub(10);
)会如何?因为参数是存在数组中的,所以给的参数多了,子程序用不到它,所以忽略多出的参数,如果给的参数少了,那么缺少的那些参数被引用时,值将是undef。
所以,在逻辑上来说,参数多少不会影响子程序的错误,但结果可能会受到一些影响。但可以在子程序中判断子程序的参数个数:
1 | if(@_ != 2){ # 如果参数个数不是2个,就退出 |
这里的比较是标量上下文,@_
返回的是参数个数。
调用子程序的几种方式分析
一般情况下,调用我们自定义的子程序时,都使用&
符号,有时候还要带上括号传递参数。
1 | &mysub1(); |
但有时候,&
符号和括号是可以省略的。主要的规则是:
- 只有子程序定义语句在子程序调用语句前面,才可以省略括号
- 当使用了括号,perl已经知道这就是一个子程序调用,这时可以省略
&
也可以不省略&
- 不能省略
&
的情况比较少。基本上,只要子程序名称不和内置函数同名,或者有特殊需求时(如需要明确子程序的名称时,如defined(&mysub)
),都可以省略&
- 不能在使用了
&
、有参数传递的情况下,省略括号 - 最安全、最保险的调用方式是:
- 有参数时:
&subname(arg1,arg2)
,即不省略&
和括号 - 无参数时:
&subname
- 使用
&
的调用方式是比较古老的行为,虽然安全。但直接使用括号调用也基本无差别,但却更现代,所以建议用func()的方式调用自定义的子程序
- 有参数时:
1 | sub mysub{ |
上面是先定义子程序,再调用子程序的。下面是先调用子程序,再定义子程序的。
1 | mysub; # 本调用无括号,不报错,当做内置函数执行,但无此内置函数,所以忽略 |
如果子程序名称和内置函数同名,则不安全的调用方式总会优先调用内置函数。
子程序中的私有变量:my关键字
my关键字可以声明局部变量、局部数组。它可以用在任何类型的语句块内,而不限于sub子程序中。
1 | sub mysub { |
在my定义局部变量的时候,需要注意列表上下文和标量上下文:
1 | @_=qw(perl shell python); |
在Perl中,除了my可以修饰作用域,还有local和our也可以修饰作用域,它们之间的区别参见:Perl的our、my、local的区别。
state关键字
my关键字是让变量、数组、哈希私有化,state关键字则是让私有变量、数组、哈希持久化。注意两个关键字:私有,持久化。
使用state关键字声明、初始化的变量对外不可见,但对其所在子程序是持久的:每次调用子程序后的变量值都保存着,下次再调用子程序会继承这个值。
这个特性是perl 5.10版才引入的,所以必须加上use 5.010;
语句才能使用该功能。
1 | use 5.010; |
当然,如果在子程序中每次都用state将变量强制初始化,那么这个变量持久与否就无所谓了,这时用my关键字的效果是一样的。
1 | use 5.010; |
state除了可以初始化变量,还可以初始化数组和hash。但初始化数组和hash的时候有限制:不能在列表上下文初始化。
1 | use 5.010; |
perl作用域初探
因为perl中支持先定义子程序再调用,也支持先调用再定义的方式。不同的调用方式有可能会有区别。
例如:
1 | #!/usr/bin/env perl -w |
上面在不同的位置调用子程序do_stuff(1)
,但只有第二种方式是正确的,第一种方式是错误的。
原因在于my定义的变量是词法作用域变量,先不用管词法作用域是什么。只需要知道my定义的变量是在程序编译期间定义的好的,但是赋值操作是在程序执行期间进行的。而子程序sub的名称do_stuff是无法加my关键字的,所以perl中所有的子程序都是全局范围可调用的。子程序的调用是程序执行期间的。
所以上面的程序中,整个过程是先在编译期间定义好词法变量$last
(但未赋值初始化),子程序do_stuff。然后开始从程序头部往下执行:
当执行到(1)的位置处也就是调用子程序,这个子程序中引用了变量$last
,但$last
至今未赋值,所以会报变量未初始化的错误。
如果没有(1),那么从上往下执行将首先执行到my $last = 1
,这表示为已在编译期间定义好的变量赋值。然后再继续执行到(2)调用子程序,但子程序引用的$last
已经赋值初始化,所以一切正常。
在perl中的子程序是在编译期间定义好的,还是执行期间临时去定义的,目前我个人还不是太确定,按照perl的作用域规则,它应该是在执行期间临时去定义的。但无论如何,它先定义还是后定义,都不影响对变量作用域的判断。