闭包(closures)这一概念在许多脚本语言,比如javascript,python,lua中普遍存在,对于长期用c/c++编程的程序员来讲,闭包的概念不是特别容易理解,也不清楚闭包的应用场景。确实,闭包的概念是面向过程编程的产物,其本质含义是一个函数和某个数据绑定在一起,数据的生命周期和函数的生命周期一样长。从上面这段话中,隐隐约约感觉到了什么?下面我们来看一下比较正式的闭包定义,并看一个简单的闭包例子。
python闭包定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
1 #!/usr/bin/env python
2
3 def fun(m):
4 def add(n):
5 return m + n
6 return add
7
8 f1 = fun(10)
9 print f1(1) #输出11
10 print f1(2) #输出12
11 print f1(3) #输出13
12
13 f2 = fun(20)
14 print f2(1) #输出21
15 print f2(2) #输出22
16 print f2(3) #输出23
闭包是定义在一个函数内部的函数,该函数引用了其外层函数的局部变量或型参变量,然后外层函数将闭包函数做为返回值返回。外面的调用方可以直接调用闭包体(比如上述代码中的f1),外层函数的局部变量(比如fun中的局部变量m)都不会消失,直到闭包体消亡。另外一个特点是定义不同的闭包体的时候,比如f1和f2,两者之间的局部变量是互相不干预的,是各自独立的一份。
根据上述例子,我们很容易将闭包跟C++的类、对象、成员函数、成员变量一一对应起来,闭包和面向对象的类本质上属于同一个思想的东西。为了说明这一点,我们举一个更加复杂的例子:
1 #!/usr/bin/env python
2 def fun(m):
3 n = [1]
4 def inc():
5 k = m[0] + n[0]
6 n[0] += 1
7 m[0] += 1
8 print k
9
10 def sub():
11 n[0] -= 1
12 m[0] -= 1
13
14 return inc, sub
15
16 f_inc, f_sub = fun([2])
17
18 times = 0
19 while True:
20 f_inc()
21 times += 1
22 if times == 10: break
23
24 times = 0
25 while True:
26 f_sub()
27 times += 1
28 if times == 10: break
29
30 times = 0
31 while True:
32 f_inc()
33 times += 1
34 if times == 10: break
fun函数返回两个闭包函数f_inc及f_sub,这两个闭包函数引用了fun里面的局部变量m及n。从面向对象的观点来看,可以将fun理解为一个类,f_inc及f_sub理解为类的成员函数,fun里面的局部变量m,n可以理解为类的成员变量。对于函数fun的调用,比如fun([2]),可以理解为类的实例化。从这些观点来理解闭包,就非常容易理解闭包的各种特性了。为了方便大家理解,大家可以参考下面这段c++代码,感觉闭包和C++的类、对象概念是不是完全一致?
1 #include
2
3 class CFun
4 {
5 public:
6 CFun(int m)
7 {
8 _n = 1;
9 _m = m;
10 }
11
12 public:
13 void f_inc()
14 {
15 int k = _m + _n;
16 _n++;
17 _m++;
18 printf("%d\r\n", k);
19 }
20
21 void f_sub()
22 {
23 _n--;
24 _m--;
25 }
26
27 private:
28 int _n;
29 int _m;
30
31 };
33 int main()
34 {
35 CFun fun(2);
36 int times = 0;
37 while(times < 10)
38 {
39 fun.f_inc();
40 times++;
41 }
42
43 times = 0;
44 while(times < 10)
45 {
46 fun.f_sub();
47 times++;
48 }
49
50 times = 0;
51 while(times < 10)
52 {
53 fun.f_inc();
54 times++;
55 }
56
57 return 0;
58 }
根据闭包的特性,可以看出其比较适合用于回调函数。因为该回调函数天然和一堆数据绑定在一起,可以省去大量的数据存储、读取工作。试想一下再面向过程编程中,若没有闭包,在设计回调函数中必然有一块数据需要单独存储,在回调时再将这块数据找出来,然后塞给这个回调函数。有了闭包后再这块可以简单很多。但在面向对象编程中,闭包基本很难发挥什么特点,对于回调我们可以设计虚函数等方式来实现数据和函数的绑定。
下面再来简单分析一下闭包的实现,整个闭包的实现和脚本语言的内存回收机制是密不可分的。对于上面的例子,可以简单理解为当 f_inc, f_sub = fun([2]) 执行时,fun里面的局部变量都被开辟,且引用计数都变为了3(f_inc,f_sub,fun都取得了一个),当执行完时,虽然fun消亡,引用计数减一,但局部变量的引用计数依旧为2,不会被系统收回。当f_inc和f_sub被定义时,其作用域链已经被确定,如下图所示。当fun([2])被执行时,相当于确定了一个执行环境,作用域链上所有的变量都会被初始化。整个概念跟类的定义及类的实例化一模一样,因此采用面向对象的观念来理解闭包,就非常容易想清楚其背后的实现原理及应用场景。
参考资料:
http://blog.csdn.net/marty_fu/article/details/7679297
阅读(1299) | 评论(0) | 转发(0) |