在Perl中,子程序的引用经常使用来作回调函数(callback)、闭包(closure),特别是匿名子程序。html
关于什么是回调函数,见一文搞懂:词法做用域、动态做用域、回调函数、闭包数组
以File::Find
模块的find函数为例,它用来搜索给定目录下的文件,而后对每一个搜索到的文件执行一些操做(经过定义子程序),这些操做对应的函数要传递给find函数,它们就是回调函数。就像unix下的find命令同样,找到文件,而后print、ls、exec CMD操做同样,这几个操做就是find命令的回调函数。闭包
use File::Find; sub cmd { print "$File::Find::name\n"; }; find(\&cmd,qw(/perlapp /tmp/pyapp));
其中$File::Find::name
表明的是find搜索到的从起始路径(/perlapp /tmp/pyapp
)开始的全路径名,此外,find每搜索到一个文件,就会赋值给默认变量$_
。它表明的是文件的basename,和$File::Find::name
全路径不同。例如:app
起始路径 $File::Find::name $_ ------------------------------------------- /perlapp /perlapp/1.pl 1.pl . ./a.log a.log perlapp perlapp/2.pl 2.pl
回到回调函数的问题上。上面的示例中,定义好了一个名为cmd的子程序,但一直都没有主动地去执行这个子程序,而是将它的引用放进find函数中,由find函数每次去调用它。这就像是unix的find命令的"-print"选项同样,其中"-print"选项对应的函数就是一个回调函数。函数
上面的子程序只调用了一次,不必花脑细胞去设计它的名称,彻底能够将其设计为匿名子程序,放进find函数中。设计
use File::Find; find( sub { print "$File::Find::name\n"; }, qw(/perlapp /tmp/pyapp) );
关于闭包的详细内容,见一文搞懂:词法做用域、动态做用域、回调函数、闭包unix
从perl语言的角度来简单描述下闭包:子程序1中返回另外一个子程序2,这个子程序2访问子程序1中的变量x,当子程序1执行结束,外界没法再访问x,但子程序2由于还引用着变量x所指向的数据对象,使得子程序2在子程序1结束后能够继续访问这个数据对象。code
因此,子程序1中的变量x必须是词法变量,不然子程序1执行完后,变量x可能仍能够被外界访问、修改,若是这样,闭包和普通函数就没有意义了。htm
一个简单的闭包结构:对象
sub sub1 { my $var1=N; $sub2 =sub { do something about $var1 } return $sub2 # 返回一个闭包 } $my_closure = sub1(); # 将闭包函数存储到子程序引用变量
主要目的是为了让子程序sub1内部嵌套的子程序$sub2
能够访问属于子程序sub1但不属于子程序$sub2
的变量,这样一来,只要把sub1返回的闭包赋值给$my_closure
,就可让这个闭包函数一直引用$var1
变量对应的数值对象,可是sub1执行完毕后,外界就没法再经过$var1
去访问这个数据对象(由于是词法变量)。也就是说,sub1执行完后,$var1
指向的数据对象只有闭包$my_closure
能够访问。
一个典型的perl闭包:
sub how_many { # 定义函数 my $count=2; # 词法变量$count return sub {print ++$count,"\n"}; # 返回一个匿名函数,这是一个匿名闭包 } $ref=how_many(); # 将闭包赋值给变量$ref how_many()->(); # (1)调用匿名闭包:输出3 how_many()->(); # (2)调用匿名闭包:输出3 $ref->(); # (3)调用命名闭包:输出3 $ref->(); # (4)再次调用命名闭包:输出4
上面将闭包赋值给$ref
,经过$ref
去调用这个闭包,则即便how_many中的$count
在how_many()执行完就消失了(由于是个词法变量,外界没法访问),但$ref
指向的闭包函数仍然在引用这个变量,因此屡次调用$ref
会不断修改$count
的值,因此上面(3)和(4)先输出3,而后输出改变后的4。而上面(1)和(2)的输出都是3,由于两个how_many()函数返回的是独立的匿名闭包,在语句执行完后数据对象3就消失了。
Perl语言有本身的特殊性,特别是它支持只执行一次的语句块(即用大括号{}
包围),这使得Perl要建立一个闭包并不必定须要函数嵌套,只需将一个函数放进语句块便可:
my $closure; { my $count=1; # 随语句块消失的词法变量 $closure = sub {print ++$count,"\n"}; # 闭包函数 } $closure->(); # 调用一次闭包函数,输出2 $closure->(); # 再调用一次闭包函数,输出3
在上面的代码中,$count
引用数在赋值时为1,在sub中使用并赋值给$closure
时引用数为2,当退出代码块的时候,count
引用数减为1,因为这是个词法变量,退出代码块后外界就没法经过$count
来访问了,可是闭包$closure
却一直能够继续访问。
闭包的形式其实多种多样。通俗意义上来讲,只要一个子程序1能够访问另外一个子程序2中的变量,且子程序1不会随子程序2执行结束就丢失变量,就属于闭包。固然,对于Perl来讲,可能子程序2并不是是必要的,正如上面的例子。
例如,下面的代码段就不属于闭包:
$y=3; sub mysub1 { $x=shift; $x+$y; } $nested_ref=\&mysub1; sub mysub2 { $x=1; $z=shift; return $nested_ref->($z); } print mysub2(2);
为mysub2中返回的$nested_ref
是一个子程序mysub1的一个实例,但mysub1中使用的$y
来自于全局变量,而非mysub2,且mysub2执行完后,$y
也不会消失,对于闭包来讲这看上去没什么必要。
例如,经过File::Find
模块的find函数,计算出给定目录下的文件数量:
use File::Find; my $callback; { my $count = 0; $callback = sub { print ++$count, ": $File::Find::name\n" }; } find($callback, '.'); # 返回数量和文件名 find($callback, '.'); # 再次执行,数量将在上一个find的基础上递增
Perl的语法强大,能够一次性返回多个闭包:
use File::Find; sub sub1 { my $total_size = 0; return(sub { $total_size += -s if -f }, sub { return $total_size }); } my ($count_em, $get_results) = sub1( ); find($count_em, '/bin'); find($count_em, '/boot'); my $total_size = &$get_results( ); print "total size of /bin and /boot: $total_size\n";
上面两个闭包,由于同时引用同一个对象,因此闭包$count_em
修改的词法变量,$get_results
也能够访问。
或者:
{ my $count=10; sub one_count{ ++$count; } sub get_count{ $count; } } one_count(); one_count(); print get_count();
因为代码块中的子程序有名称,因此这两个子程序在代码块结束后仍然有效(代码块结束后变量无效是由于加了my修饰符)。
可是,若是将调用语句放在代码块前面呢?
one_count(); # 1 one_count(); # 2 print get_count(); # 输出:2 { my $count=10; sub one_count{ ++$count; } sub get_count{ $count; } }
上面输出2,也就是说$count=10
的赋值10行为失效。这是由于词法变量的声明和初始化(初始化为undef)是在编译期间完成的,而赋值操做是在执行到它的时候执行的。因此,编译完成后执行到one_count()
这条语句时,将调用已编译好的子程序one_count,但这时尚未执行到语句块,因此$count
的赋值尚未执行。
能够将上面的语句块加入到BEGIN块中:
one_count(); # 11 one_count(); # 12 print get_count(); # 输出:12 BEGIN{ my $count=10; sub one_count{ ++$count; } sub get_count{ $count; } }
前面闭包的做用已经很是明显,就是为了让词法变量不能被外部访问,但却让子程序持续访问它。
perl 5.10提供了一个state修饰符,它和my彻底同样,都是词法变量,惟一的区别在于state修饰符使得变量持久化,但对于外界来讲却不可访问(由于是词法变量),并且state修饰的变量只会初始化赋值一次。
注意:
例如,将state修饰的变量从外层子程序移到内层自层序中。下面两个子程序等价:
use 5.010; # for state sub how_many1 { my $count=2; return sub {print ++$count,"\n"}; } sub how_many2 { return sub {state $count=2;print ++$count,"\n"}; } $ref=how_many2(); # 将闭包赋值给变量$ref $ref->(); # (1)调用命名闭包:输出3 $ref->(); # (2)再次调用命名闭包:输出4
须要注意的是,虽然state $count=2
,但同一个闭包屡次执行时不会从新赋值为2,而是在初始化时赋值一次。
并且,将子程序调用语句放在子程序定义语句前面是能够如期运行的(前面分析过通常的闭包不会如期运行):
$ref=how_many2(); # 将闭包赋值给变量$ref $ref->(); # (1)调用命名闭包:输出3 $ref->(); # (2)再次调用命名闭包:输出4 sub how_many2 { return sub {state $count=2;print ++$count,"\n"}; }
这是由于state $count=2
是子程序中的一部分,不管在哪里调用到它,都会执行这一句赋值语句。
再例如,state用于while循环的语句块内部,使得每次迭代过程当中都持续访问这个变量,而不会每次迭代都初始化:
#!/usr/bin/perl use v5.10; # for state while($i<10){ state $count; $count += $i; say $count; # 输出:0 1 3 6 10 15 21 28 36 45 $i++; } say $count; # 输出空