Perl回调函数和闭包
在Perl中,子程序的引用常用来做回调函数(callback)、闭包(closure),特别是匿名子程序。 回调函数(callback)关于什么是回调函数,见一文搞懂:词法作用域、动态作用域、回调函数、闭包 以 use File::Find; sub cmd { print "$File::Find::namen"; }; find(&;cmd,qw(/perlapp /tmp/pyapp)); 其中 起始路径 $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::namen"; },qw(/perlapp /tmp/pyapp) ); Perl闭包(closure)简单介绍关于闭包的详细内容,见一文搞懂:词法作用域、动态作用域、回调函数、闭包 从perl语言的角度来简单描述下闭包:子程序1中返回另一个子程序2,这个子程序2访问子程序1中的变量x,当子程序1执行结束,外界无法再访问x,但子程序2因为还引用着变量x所指向的数据对象,使得子程序2在子程序1结束后可以继续访问这个数据对象。 所以,子程序1中的变量x必须是词法变量,否则子程序1执行完后,变量x可能仍可以被外界访问、修改,如果这样,闭包和普通函数就没有意义了。 一个简单的闭包结构: sub sub1 { my $var1=N; $sub2 =sub { do something about $var1 } return $sub2 # 返回一个闭包 } $my_closure = sub1(); # 将闭包函数存储到子程序引用变量 主要目的是为了让子程序sub1内部嵌套的子程序 一个典型的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 上面将闭包赋值给 Perl语言有自己的特殊性,特别是它支持只执行一次的语句块(即用大括号 my $closure; { my $count=1; # 随语句块消失的词法变量 $closure = sub {print ++$count,"n"}; # 闭包函数 } $closure->(); # 调用一次闭包函数,输出2 $closure->(); # 再调用一次闭包函数,输出3 在上面的代码中, 闭包的形式其实多种多样。通俗意义上来说,只要一个子程序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中返回的 Perl闭包应用例如,通过 use File::Find; my $callback; { my $count = 0; $callback = sub { print ++$count,": $File::Find::namen" }; } 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_sizen"; 上面两个闭包,因为同时引用同一个对象,所以闭包 或者: { 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,也就是说 可以将上面的语句块加入到BEGIN块中: one_count(); # 11 one_count(); # 12 print get_count(); # 输出:12 BEGIN{ my $count=10; sub one_count{ ++$count; } sub get_count{ $count; } } state修饰符替代简单的闭包前面闭包的作用已经非常明显,就是为了让词法变量不能被外部访问,但却让子程序持续访问它。 perl提供了一个state修饰符,它和my完全一样,都是词法变量,唯一的区别在于state修饰符使得变量持久化,但对于外界来说却不可访问(因为是词法变量),而且state修饰的变量只会初始化赋值一次。 注意:
例如,将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 需要注意的是,虽然 而且,将子程序调用语句放在子程序定义语句前面是可以如期运行的(前面分析过一般的闭包不会如期运行): $ref=how_many2(); # 将闭包赋值给变量$ref $ref->(); # (1)调用命名闭包:输出3 $ref->(); # (2)再次调用命名闭包:输出4 sub how_many2 { return sub {state $count=2;print ++$count,"n"}; } 这是因为 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |