C++雾中风景7:闭包
本来说好要聊一聊命名空间的,因为最近在看C++lambda表达式的内容,所以借这个机会我们来好好聊一聊C++的闭包。
1.什么是闭包?
闭包(closure)是函数式编程的重要的语法结构。
闭包的概念其实很简单,一言以蔽之:闭包是带有上下文的函数。说白了,就是有状态的函数。也就是说一个局部变量n,在被函数对象给“封闭”在函数里,从而能把值保存了下来,让函数能够保存状态。(其实本质上就是一个类,用纯粹面向对象的方式理解,函数也是一个对象)
扯概念很烦,我们直接上代码来看一看。这里我们用Python的代码来解释一下闭包。(后续我们再来详细聊聊C++之中是怎么样实现闭包的):
def getFun(n):
return lambda:n + n
funA = getFun(10)
funB = getFun("abc")
print(funA.func_closure[0].cell_contents)
print(funB.func_closure[0].cell_contents)
打印结果为:
10 //funcA的闭包变量
abc //funcB的闭包变量
由上述两个函数我们看到变量作为一种状态嵌入到函数由getFun返回的函数之中。不同的语言实现闭包的方式不同。Python以函数对象为基础,为闭包是通过函数对象的属性来保存闭包的变量。(这里在Python之中是一个tuple,从这里也可以看出,所谓的闭包本质上就是类属性的一个语法糖。)
这里闭包解决了编程工作之中的两个痛点:
- (1)突破了函数访问变量的作用域。
- (2)可以动态添加函数属性,真是通过这种动态特性,可以让我们实现某些编程任务的时候变得很简洁。
- (3)函数可定制化更佳,提高了函数的可移植性。
闭包的作用有很多,可以在python上实现动态代理,如装饰器等.......这里就不展开聊闭包的使用,接下来我们要来重点看看在C++之中是如何实现闭包的。
2.C++之中的闭包
C++相对于C的优越点就在于C++能够支持面向对象的特性,C语言之中在语法层面是不能支持闭包的。我们来看看C++之中有几种方式来支持闭包特性:
- 重载类的operator() 操作符
第一次看到用法的时候有点震惊,没想到重载()括号操作符之后可以将普通的类转变为Callable对象,当时觉得很Tricky。这种用法其实本质上是其他语法糖的基础,我们来看一看代码:
class Closure {
public:
Closure(int n):num(n){};
int operator()(int add) {
return num + add;
}
private:
int num;
};
int main() {
Closure clu(20);
cout << clu(50) << endl;
}
可以看到,重载了()操作符的类Closure摇身一变成为了一个函数,可以直接被调用。同时它也包含了对象成员,通过对象成员保存下来了函数的运行状态。
- lambda表达式
喜欢函数式编程的同学最喜欢使用的工具了(C++11对于C++来说是一个很重要的版本),lambda表达式可以很方便的让我们定义一个匿名函数,我们来看看怎么用lambda表达式来实现闭包:
int main() {
int num = 20;
function<int(int)> clu = [num](int add) {return num + add;};
cout << clu(50) << endl;
}
使用lambda表达式实现同样的功能,代码就简洁明了许多。这里的clu是通过一个匿名类来实现的,所以每个lambda表达式都是独一无二的,我们只能使用function或auto来捕获它。这里lambda表达式通过[]操作符捕获外部变量,并且和函数绑定在了一起。
- 参数绑定
bind函数在C++11之中也被加入了标准库,我们来看看通过参数绑定是如何实现闭包的:
int addNum(int num,int add) {
return add + num;
}
int main() {
auto clu = bind(addNum,placeholders::_1,20);
cout << clu(50) << endl;
}
通过bind函数,将20绑定到对应的参数add之上,而每次调用clu函数之时,参数会对应到_1的位置,也就是函数addNum的第一个参数num。通过bind的函数,我们可以将外部变量与和原函数绑定在了一起,并且生成了一个新的函数对象。
好的,关于C++之中的闭包就和大家聊到这里,希望大家在实际Coding之中可以用好它........