+ -
当前位置:首页 → 问答吧 → perl精粹解析 - closure 与 静态局部变量

perl精粹解析 - closure 与 静态局部变量

时间:2011-01-05

来源:互联网

一.什么是closure

perl 中有一个概念叫“closure”(闭环,来源于数学):

假如有一个subroutine, 它访问了在其外部声明的私有变量,那么这个subroutine就叫做closure.

例如:

Perl code
{
  my $count = 0;
  sub callback { print ++$count; };
  sub get_count{return $count;};
}
&callback();  //此时, $count = 1
&callback();  //此时, $count = 2

这里,callback和get_count就是一个closure,因为它访问了在其外部声明的私有变量$count.

closure在实际应用中提供了c语言中局部静态变量的功能。

它有以下特性:

1.访问的变量是私有变量,保证只有少数subroutine能访问到该变量,这些具有访问权限的subroutine就构成了一个环,因此也叫闭环;

  在上面的例子中,$callback和get_count构成了能访问$count的环;

  由于环中的subroutine都可以访问$count(与c语言中的静态变量作用一样),使得每一次访问都可以改变$count的值。

  在这个例子中,第一次调用&callback()后, $count的值变为1,第2次调用后,值变为2.

2.该私有变量的生命周期是与环中的生命周期最长的subroutine一样。这是通过perl中的引用计数机制来实现的:

  上面的例子中,在括号内的代码部分, $count的引用计数是3,出了括号,$count的引用计数变成了2,

  callback生命周期结束的时候,$count的引用计数减去1,此时变为1

  get_count生命周期结束的时候,$count的引用计数减去1,此时变为0  

  当$count的引用计数变为0之后,被perl回收。

二.perl中的closure与C语言中静态局部变量的区别

  1.C语言本身提供了静态局部变量机制--static关键字,程序员在使用该功能特性的时候更方便;

  perl本身要实现同样的功能,需要程序员额外的努力,在第三部分perl中closure的常见用法中,

  大家就可以看到这些技巧。

  2. C语言中,只有静态局部变量所在的函数对该变量具有访问权;

  而perl中,闭环中的所有subroutine都可以访问和修改“静态变量”。

  perl的这个特性,使得它在某些情况下,比c语言更加便利。参见下节中的用法一。

三.perl中closure的常见用法

  以下摘自“Learning Perl Objects, References & Modules” 的第6章:

  用法一 在subroutine中返回subroutine的引用,通常作为回调函数:

Perl code
use File::Find;
sub create_find_callbacks_that_sum_the_size {
  my $total_size = 0;
  return(sub { $total_size += -s if -f }, sub { return $total_size });
}
my ($count_em, $get_results) = create_find_callbacks_that_sum_the_size(  );
find($count_em, "bin");  //寻找bin目录下所有的文件,并对找到的所有文件执行回调函数$count_em
my $total_size = &$get_results(  );
print "total size of bin is $total_size\n"
这段代码用于计算某个目录下所包含的所有文件的大小之和.

  这里,create_find_callbacks_that_sum_the_size返回了两个subroutine的引用,一个用来计算

total_size的值,一个用来获取total_size的值.

  create_find_callbacks_that_sum_the_size相当于函数生成器,生成了两个subroutine。

  如果用c语言来实现同样的功能,有两种方法:

  1.改变find函数的接口,增加参数total_size,当find函数返回时,设置total_size作为真正的返回值

C/C++ code
      #define int (* SUM)(int);
     int get_sum(int filesize);
     {
         static int count = 0;
         count += filesize;
         return count;
     }
      bool find(SUM callback, char *path, int pathLen, int *total_size )

  这里假设callback的返回值是total_size,那么find可实现如下:

  C/C++ code
bool find(SUM callback, char *path, int pathLen, int *total_size )
    {
          bool result = false;
         for(...) //寻找path下的每一个文件
          {
             ...
              if (file is find)
              {
                   total_size = callback(filesize);  //get_sum取代形参callback
                   result = true;
               }
          }
          return result;
     }

  此时,要想获取totalsize的值,只需要执行:

  C/C++ code
int totalsize = 0;
     find(get_sum, path, pathlen, &totalsize); 

  2.改变callback函数的接口

  C/C++ code
#define int (* SUM)(int, bool);
      int get_sum(int filesize, bool ret = false);
      {
          static int count = 0;
          if (!ret)
          {
              count += filesize;
          }
          return count;
      }
      bool find(SUM callback, char *path, int pathLen)
    {
         bool result = false;
        for(...) //寻找path下的每一个文件
          {
             ...
              if (file is find)
              {
                   callback(filesize);  //get_sum取代形参callback
                   result = true;
               }
          }
          return result;
     }

  此时,要想获取totalsize的值,只需要执行:

C/C++ code
     find(get_sum, path, len);
     int totalsize = get_sum(anyinteger, true);

  对方法一和方法二进行小结:

  1. 方法一改变了find函数的接口,而通常find函数是系统提供的库函数,接口无法改变.

  2. 方法二更糟糕,由于改变了回调函数get_sum的接口,函数指针SUM的类型也发生了改变(增加了一个bool型参数)

  , 导致所有被find所调用到的SUM类型的回调函数都需要改变接口和实现,这对代码的扩展和维护来说简直是个灾难!

  相比之下,用perl实现同样的功能更加简洁通用,扩展性更好,对现有的代码影响更小。

  用法二 使用闭环变量作为输入,用作函数生成器,来生成不同的函数指针:

Perl code
use File::Find;
sub print_bigger_than {
  my $minimum_size = shift;
  return sub { print "$File::Find::name\n" if -f and -s >= $minimum_size };
}
my $bigger_than_1024 = print_bigger_than(1024);
find($bigger_than_1024, "bin");



  print_bigger_than在这里相当于一个函数生成器,不同的输入变量可以生成不同的函数指针.

  这里生成了一个可以打印出文件大小大于1024字节文件名的回调函数.



  用法三 作为静态局部变量使用,提供了c语言静态局部变量的功能:

   

Perl code
BEGIN {
  my $countdown = 10;
  sub count_down { $countdown-- }
  sub count_remaining { $countdown }
}



  这里用到了关键字BEGIN.

  BEGIN的作用就是,当perl编译完这段代码之后,停止当前编译,然后直接进入运行阶段,执行BEGIN块内部的代码.然后再回到编译状态,

  继续编译剩余的代码.

  这就保证了无论BEGIN块位于程序中的哪个位置,在调用count_down之前,$countdown被确保初始化为10.

作者: mac_philips   发布时间: 2011-01-05

原创?现在很少有代码在调用函数的时候前面加&了,&callback()一般都写callback()。另外“私有变量”并不准确,closure和私有与否没什么联系。准确些讲是局部变量(或者说局部引用)。闭环也是编程里很少用的术语,一般叫闭包比较多。对C语言的比较那一部分也不是很到位,closure是动态语言才有的,最大的优势在于可以动态创建新的closure,这是与C语言最大的区别。

作者: iambic   发布时间: 2011-01-05