子例程引用与闭包:

和普通变量一样,子例程(subroutine)可以是有名的也可以是匿名的,而且Perl语法上支持对任何一种类型的引用。

1. 调度表(dispatch table),它是一种将事件映射到子例程引用的数据结构。

每当事件发生时,就会通过一张调度表来查找与之相关的子例程。


2.闭包(Closure),闭包是这样一种子例程,创建时,它将包含其子例程的环境打包(包括所有它需要的和不局部与其本身的变量)。

子例程引用:

对有名子例程的引用:

我们前面就已经讲过,要创建对现存变量的引用,只需给它加上反斜杠前缀。

对于子例程也大致如下,如&mysub就是对&mysub的引用


[oracle@oadb 4]$ cat a1.pl 
sub greet {
   print "hello
";
};

$rs=&greet; ##创建对子例程greet的引用

&$rs;

[oracle@oadb 4]$ perl a1.pl 
hello


[oracle@oadb 4]$ cat a2.pl 
sub greet {
   print "hello
";
};

$rs=&greet(); ##创建对子例程greet的引用

&$rs;

[oracle@oadb 4]$ perl a2.pl 
hello
Not a CODE reference at a2.pl line 7.
[oracle@oadb 4]$ 


对匿名子例程的引用:

你只需省略子例程声明中的过程名即可创建匿名子例程,除了这一点外,其声明同有名字例程完全一样:

[oracle@oadb 4]$ cat a3.pl 
$rs=sub{
   print "hello 
";
};

print &$rs;
[oracle@oadb 4]$ perl a3.pl 
hello 
1[oracle@oadb 4]$ 


对子例程引用的间接访问:


对子例程引用的间接访问(dereference)将会间接的调用该子例程。

和数据引用一样,Perl并不关心$rs指向的有名还是无名的子例程。

[oracle@oadb 4]$ cat a4.pl 
sub greet {
   my $a=shift;
   my $b=shift;
   return $a+$b;
};

my $rs=&greet;

print &$rs(10,20);
[oracle@oadb 4]$ perl a4.pl 
30[oracle@oadb 4]$ 

[oracle@oadb 4]$ cat a4.pl 
sub greet {
   my $a=shift;
   my $b=shift;
   return $a+$b;
};

my $rs=&greet;

print $rs->(10,20);
[oracle@oadb 4]$ perl a4.pl 
30[oracle@oadb 4]$ 


使用子例程引用:

让我们来看一些子例程引用的常见例子:回调函数与高等级子例程:

回调函数就是通过引用来使用的普通子例程。




调度表 

典型的调度表就是一个包含子例程引用的数组

[oracle@oadb 4]$ cat a5.pl 
 %options = (   #针对每个选项调用相应的子例程
     "-h" =>&help,
     "-f" =>sub{$a='abcdef'},
     "-r" =>sub {$b=1}
);

sub ProcessArgs {
  ##注意记号的意义,r1表示指向数组的引用,rh表示指向散列表的引用
  my $rlArgs=shift;
  my $rhOptions=shift;
  foreach $arg (@$rlArgs){
    if (exists $rhOptions->{$arg}){
         #其值一定是指向子例程的引用
         $rsub = $rhOptions->{$arg};
         print &$rsub;
         print "
";
}
}
};

my @arr=("-h","-f");

sub help{
   return 78 + 21;
};

&ProcessArgs(@arr,\%options);


    
[oracle@oadb 4]$ perl a5.pl 
99
abcdef



闭包:

不仅可以返回数据,Perl的子例程还可以返回一个指向子例程的引用。

[oracle@oadb 4]$ cat a6.pl 
use Data::Dumper;
$greeting ="hello world";
$rs=sub {
   print $greeting;
};

print $rs;
print "
";
print Dumper($rs);
print "
";

print &$rs;
[oracle@oadb 4]$ perl a6.pl 
CODE(0x1b0a660)
$VAR1 = sub { "DUMMY" };

hello world1[oracle@oadb 4]$ 

在这个例子中,匿名子例程利用了全局变量$greeting


[oracle@oadb 4]$ cat a7.pl 
sub generate_greeting {
   my $greeting="hello world";
   return sub{print $greeting};
};

$rs=generate_greeting();
&$rs();
[oracle@oadb 4]$ perl a7.pl 
hello world[oracle@oadb 4]$ 
[oracle@oadb 4]$ 


让我们再来改动一下这段代码,以更好地理解闭包这个专业用语究竟能做什么:

[oracle@oadb 4]$ cat a8.pl 
sub generate_greeting {
    my $greeting = shift;
    return sub {
      my $subject=shift;
      print "$greeting $subject abcdefg
";
   }
};

$rs1=generate_greeting("hello");

print &$rs1;
print "
";
print &$rs1("world");

  
[oracle@oadb 4]$ perl a8.pl 
hello  abcdefg
1
hello world abcdefg
1[oracle@oadb 4]$ 


闭包的内幕:

闭包的应用:

智能回调
当用户按下按钮时,回调过程将被调用,并使用它所保存的$title的值。  
  
use Tk;  
##创建顶层窗口  
my $mw = MainWindow->new(-title => "Mem monitor");  
##创建两个按钮,这些按钮将在被按下打印自己的名字  
CreateButton($mw,"hello");  
  
CreateButton($mw,"world");  
  
sub CreateButton {  
   my ($parent,$title)=@_;  
   my $b;  
   $callback_proc=sub {  
           print "Button $title pressed
";  
           };  
      
     $frm_name1 = $mw->Frame()->pack(-side=>"top",-fill => 'x');  
    $b=$frm_name1->Button (  
         '-text'=>"title",  
         '-fg'=>'red',  
         '-command'=>$callback_proc  
);  
    $b->pack();  
};  
MainLoop;  
             
  
C:UsersTLCBDesktopperl>perl testtk.pl  
Button hello pressed  
Button hello pressed  
Button hello pressed  
Button world pressed  
Button world pressed  
Button world pressed  
Button world pressed  
Button world pressed  
Button world pressed