闭包里的自由变量

闭包(closure),是一种编程语言特性,它指的是代码块和做用域环境的结合,早期由scheme语言引入这一特性,随后几乎全部语言都带有这一特性,典型的闭包以下:javascript

(define (func a)
  (lambda (b) 
     (lambda (c) (+ a b c))))
(((func 1) 2) 3)复制代码

闭包里的自由变量会绑定在代码块上,在离开创造它的环境下依旧生效,而这一点使用代码块的人可能没法察觉。php

闭包里的自由变量的形式有不少,先举个简单例子。java

function add(one){
    return function(two){
        return one+two;
    }
}
const a = add(1);
const b = add(2);复制代码

在上面的例子里,a和b这两个函数,代码块是相同的,但如果执行a(1)和b(1)的结果倒是不一样的,缘由在于这二者所绑定的自由变量是不一样的,这里的自由变量其实就是函数体里的one。c++

add这个函数嵌套返回一个新的函数,而新的函数也带来了新的做用域,在JS里,自由变量的查找会从本级做用域依次向外部做用域,直到查到最近的一个,而自由变量的绑定也会在函数定义的时候就已经肯定,这也是词法做用域(或称静态做用域)的具体表现。编程

自由变量的引入,能够起到和OOP里的封装一样做用,咱们能够在一层函数里封装一些不被外界知晓的自由变量,从而达到相同的效果,举例说这么一个简单的java类。闭包

class Demo{
    private int r;
    private int k = 1;
    public Demo(int r){
       this.r = r;
    }
    public int getSquare(){
        return this.r*this.r*this.k;
    }
    public void incr(){
       this.k++;
    }
}复制代码

这里的变量r被封装了,咱们能够new Demo(1)或者new Demo(2)返回不一样的实例,而后调用相同的方法来获得不一样的结果,这一点若是用自由变量也能够作到。编程语言

func demo(r int) (func() int,func()){
    k := 1
    getSqueare := func() int{
        return r*r*k
    }
    incr := func (){
        k++
    }
    return getSqueare,incr
}
getSqueare,incr := demo(1)复制代码

在执行demo(1)或者demo(2)的时候,获得的对象均可以用来执行相同的方法,然而他们的自由变量(r和k)都是相互隔离的,这就是封装的表现。函数

自由变量的肯定在其余语言有着不同的表现,好比说php里,函数与函数之间的做用域是彻底隔离的,除非你用传参或者global来拿到外部做用域的变量,这会致使咱们作封装的时候极为麻烦,因此php5.3里加了use语法,它容许在函数做用域里引用上一层的自由变量。工具

好比上面的JS代码能够改为这样的php代码。ui

function demo($r){
   $k = 1;
   return array(
      "getSquare"=>function() use ($r,&$k){
         return $r*$r*$k;
      },
      "incr"=>function() use (&$k){
         $k++;
      }
   );
}复制代码

这里还有一个要注意的地方就是在use $k的时候,用了&表示按引用传递,由于若是不这么作的话,内部函数里的这个$k实际上只是一份值拷贝,没法改变其值,也没法应用改变以后的新值。

有人比较偏心php 的use语法,由于这样能够明确的肯定须要使用的外部自由变量,而有的人偏心js这种隐式写法,缘由是写起来简洁不累赘,只能说语言设计都有不一样的取舍。

刚才说到的这些,其实函数都是一等公民的状况下,然而在其余形式的语言里,其实也都有闭包,好比说在java里,虽然没法定义一个脱离于class的函数,可是咱们能够在method里的内部定义一个class,这个class也就是local class,它实际上就是一种闭包,举例来讲。

class Demo {
  private volatile int a;
  public void test(final int b) {
    new Thread(
      new Runnable() {
        void run() {
          a++;
          System.out.print(b);
        }
      }
    ).start();
  }
}复制代码

上面test方法里的local class,能够直接引用或者更改定义在类里的private variable,也能够读取方法里的参数,而且它的自由变量绑定也是在定义的时候就已经肯定好的。
然而因为java自己的限制,因此上面的参数b必须是final的,这一点在java8的lambda也不例外,就算在java8里你不使用final肯定,它仍是隐式的认为其是final,因此没法在local class里的方法更改这个参数。

再说说C++,C++里最开始是使用运算符重载来达到定义函数类型,可是它有一个缺点就是没法捕获外部的自由变量,为了达到相同的效果,你须要使用一个重载了()操做符的类对象来做为伪函数,好比这样:

class Func{
Func(int one){ this->one = one; }  
public:
    int operator ()(int two) {  
        return this.one+two;  
    }
private: 
    int one;
};
Func f = Func(3); 
f(10);复制代码

C++11加入了lambda语法糖,能够很容易的捕获自由变量:

int one = 3;
auto f = [=](int two){
    return one+two;
};
f(10);复制代码

在上面的[=](int two) {}语句里,咱们可使用外部的自由变量one,C++里能够选择=、&来指示是不是引用仍是捕获值,或者指定任意要捕获的变量名,这一点能够极大方便咱们在C++里使用函数。
然而在C语言里,咱们想要作一样的事情就很困难了,C语言并不支持高阶函数,咱们想要让函数能做为参数代入,或者让函数可以返回函数,咱们须要使用函数指针,典型的函数指针是这样子的:

int (*funcP) (int,int);
(*funcP)(1,2);复制代码

这里的funcP本质上是一个指针,因此它能够在C语言里被函数当作参数或者当作返回值,然而它没法代入自由变量,也就是说它根本无法作到捕获做用域变量,咱们若是想要使用外层变量,必须手动加入一个参数的指针,而后再和函数指针一块儿代出去,这样才能使用到外层的变量。

何其麻烦!因此不少厂商为c语言定制了闭包特性,其中比较有名的就是苹果家的block,它的定义形式和函数指针极为类似,只不过把*换成了^,然而它却有闭包的特性,能够捕获自由变量,举例来讲:

int a =10;
int main(void){
  int (^op) (int);
  int b = 20;
  static c = 30;
  op = ^(int one){ return one+a+b+c;};
  op(1);
}复制代码

咱们上面的op是一个block,在它的内部,能够捕获到全局变量a,以及局部变量b,静态变量c,对于全局变量a以及局部静态变量c,它是能够直接访问而且能够修改的,然而对于局部变量b,它却只能访问,而没法作修改,而且当b的值发生变化的时候,它也没法感知,其实是由于捕获的时候就已经把b的值代入进栈的block object里了。

为了可以更好的捕获自由变量,因此block还引入了一个特殊的修饰符,也就是__block,用于修饰局部非静态变量,被__block修饰的变量是能够在block里读取并修改的,它的值是动态生成的,实质上是每次执行的时候都会去获取被修饰变量的内存区域,从而达到共享变量值的效果。

block object还有一个比较重要的地方就是它和其余变量同样,生命周期在定义的函数执行结束以后也就结束了,这样咱们须要考虑的就是如何脱离创造它的环境下依旧有效,block引入了Block_copy这一工具函数,用于将栈上的block复制到堆上,这样新的block就能够脱离原有的创造环境了。

总之,闭包在各类语言上有着不一样的语法语义,其核心要素就是在于自由变量如何捕获,咱们在使用闭包的时候须要注意到语言的做用域方式,以及自由变量捕获方式这些特色。

相关文章
相关标签/搜索