Lambda表达式和闭包Closure

来源:互联网 发布:淘宝等级v2在哪里看 编辑:程序博客网 时间:2024/06/06 02:09

所谓闭包Closure,就是一种将函数及其函数调用环境组成在一起的实体。还有一种概念是匿名函数,匿名函数与闭包一样给程序员提供了一种简单顺手的函数快捷定义方法,但闭包多出来的功能是:在定义函数时,还会将周边环境数据一起传入该函数中。比如C++ Lambda函数就是闭包,在定义Lambda函数时可认为在创建一个重载了()符号的类的实例,而周边环境数据作为该类的成员变量,在构造函数中传给实例中的成员。当真正调用Lambda函数时,则调用该实例的()函数。

C++ Lambda的格式和使用

4种格式:
[ capture-list ] ( params ) mutable(optional) exception attribute -> ret { body }
[ capture-list ] ( params ) -> ret { body }
如:[] (int i) -> int { return i*i; }
[ capture-list ] ( params ) { body }
如:[](int i) -> { return i*i; }
如果该函数返回值是void,或者返回值由函数实现体中的return语句决定,则可以省去”-> ret”
[ capture-list ] { body }

实际上[]是Lambda函数的引出符,编译器看到这个符号才会认为它是Lambda函数。内部的capture-list定义了Lambda函数内部如何访问该函数外部的那些变量,Lambda函数能够捕捉外部变量是它不同于其它普通函数的一个最大特点。
capture-list有0、1或多个下列符号组成,如果是多个,则用逗号“,”分割,可以看成是参数实参。
var表示以值方式捕捉外部变量var=表示以值方式捕捉父作用域的所有外部变量&var表示以引用方式捕捉外部变量var&表示以引用方式捕捉父作用域的所有外部变量this表示以值方式捕捉外部作用域this指针
如:

void testLambda(){    int i = 4 ;    int j = 5 ;    int result = [ i](int k)-> int {         return i * k;    } (8 );    std::cout << "test Lambda result = " << result << std:: endl;}

在很多情况,需要将lambda函数作为回调函数传入别的地方,可以使用函数指针接出lambda函数,并在其他地方调用。注意:使用函数指针时,不能使用capture list。

typedef int (*funcptr)( int);funcptr test(){    auto myfunc = []( int x) { return x *x; };    return myfunc ;}int _tmain( int argc, _TCHAR* argv []){    funcptr func = test();    std::cout << "result = " << func (9) << std ::endl;    return 0;}

问题1:当使用[var]时,var必须要是父作用域的自动变量吗?这个var使用全局变量,可以吗?
答:不行,必须是父作用域的自动变量
问题2:Lambda函数作为回调函数被创建后,又在别地被调用,那在定义的时候所capture到的一些局部变量还有用吗?
答:当试图将Lambda函数转化成函数指针的时候,必须要求函数没有capture任何东西。

Lambda函数与函数对象(functor)

函数对象functor是由C++标准库提供的,就是一个提供了operator()成员函数的类,当用户调用该对象的()函数时,看上去就像调用一个函数名是对象名的函数一样。

class Calculate{public:    Calculate () {};    int operator()(int i, int j ) { return i + j; }};int _tmain( int argc, _TCHAR* argv []){    Calculate cal ;    std::cout << "5 + 8 = " << cal(5, 8) << std ::endl;    return 0;}

这里的cal(5, 8),看上去好像是一个函数名为cal、参数为5和8的函数,但实际上,cal却是一个函数对象,它包含了一个()函数。

那么问题来了,采用函数对象有什么好处呢?我觉得好处在于:一方面将函数封装成一个对象,可以代替函数指针,在不同地方进行传递,更容易理解;另一方面,函数对象可以成为有状态的函数。一般而言,函数是没有状态的,除非了内部使用了static变量,但函数对象,可以利用其内部成员变量而不是静态变量来保存状态,更加方便。如下面的代码,就对上面的例子进行了增强,使得其中的运算结果还将上了原来的一个base,从而可以用来实现累加。

class Calculate{public:    Calculate() { m_sum = 0 ; };    int operator()(int i , int j )    {        m_sum += ( i + j);        return m_sum;    }private:    int m_sum ; };int _tmain( int argc , _TCHAR * argv []){    Calculate cal ;    std ::cout << "5 + 8 = " << cal (5, 8) << std :: endl;    std ::cout << "plus 12 + 9 = " << cal (12, 9) << std :: endl;    return 0 ;}

此时结果为34,则第一次运行的结果将被保存在cal对象中,等再次运行cal,就会进行将当前计算结果在原来结果上累加。也就是说,实际上每次的执行,都有一个初始状态,在cal运算中,这个初始状态就是上次调用的结算结果。这看起来非常像lambda的capture的值,实际上Lambda就是使用Functor实现的。

编程范式:命令式编程Imperative Programming,面向对象编程Object-oriented Programming,函数式编程Functional Programming。

0 0