C++11的Lambda函数

Lambad函数概念

参考:《深入理解C++11:新特性解析与应用》

C++11 中新增了 Lambda 匿名函数,其基本格式如下:

[捕捉列表] (参数) mutable -> 返回值类型 {函数体}

参数说明:

  1. [] 是 Lambda 的引出符,捕捉列表能够捕捉上下文中的变量,来供 Lambda 函数使用,变量捕捉规则如下:

[var] 表示以值传递方式捕捉变量 var

[=] 表示值传递捕捉所有父作用域变量

[&var] 表示以引用传递方式捕捉变量 var

[&] 表示引用传递捕捉所有父作用域变量

[this] 表示值传递方式捕捉当前的 this 指针

还有一些组合:

[=, &a] 表示以引用传递方式捕捉 a,值传递方式捕捉其他变量

注意:

捕捉列表不允许变量重复传递,如:[=, a][&, &this],会引起编译时期的错误

  1. 参数列表与普通函数的参数列表一致。如果不需要传递参数,可以联连同 () 一同省略

  2. mutable 可以取消 Lambda 的常量属性,因为 Lambda 默认是 const 属性,multable 仅仅是让 Lamdba 函数体修改值传递的变量,但是修改后并不会影响外部的变量。

  3. -> 返回类型如果是 void 时,可以连 -> 一起省略。如果返回类型很明确,可以省略,让编译器自动推倒类型。

  4. 函数体和普通函数一样,除了可以使用参数之外,还可以使用捕获的变量。

通过上面呢规则说明,可以得出,最简单的 Lambda 函数可以是如下:

[] {}

实例:

int main(int argc, char* argv[])
{
    int a = 5, b = 7;
    auto total = [](int x, int y)->int { return x + y; };    //接受两个参数
    cout << total(a, b) << endl;  //12

    auto fun1 = [=] { return a + b; };   //值传递捕捉父作用域变量
    cout << fun1() << endl; //12

    auto fun2 = [&](int c) { b = a + c; a = 1; };    //省略了返回值类型,引用捕获所有
    fun2(3);    //1 8
    cout << a <<" "<< b << endl;

    a = 5; b = 7;   //被修改后,重新赋值
    auto fun3 = [=, &b](int c) mutable { b = a + c; a = 1; };    //以值传递捕捉的变量,在函数体里如果要修改,要加mutaple,因为默认const修饰
    fun3(3);
    cout << a << " " << b << endl;    //5,8

    a = 5; b = 7;   //被修改后,重新赋值
    auto fun4 = [=](int x, int y) mutable->int { a += x; b += y; return a + b; };
    int t = fun4(10, 20);
    cout << t << endl;  //42
    cout << a <<" "<< b << endl;    //5 7
    return 0;
}

块作用域以外的 Lambda 函数捕捉列表必须为,因此这样的函数除了语法上的不同,和普通函数区别不大。

块作用域以内的 Lambda 函数仅能捕捉块作用域以内的自动变量,捕捉任何非此作用域或非自动变量(静态变量),都会引起编译器报错:

static int s = 10;
auto func = [s]{ cout << s << endl; } //错误,无法在lambda中捕获带有静态变量

即使改为引用捕获依旧会报错。

Lambda函数与仿函数

在 C++11 之前,STL 中的一些算法需要使用一种函数对象——仿函数 (functor)。其本质是重新定义和成员函数 operator() ,使其使用上很像普通函数。其实,细心的我们已经发现,Lambda 函数与仿函数似乎有一些默契。

如下例子:折扣

class Price
{
private:
    float _rate;

public:
    Price(float rate) : _rate(rate) {}
    float operator()(float price)
    {
        return price * (1 - _rate / 100);
    }
};

int main(int argc, char *argv[])
{
    float rate = 5.5f;

    Price c1(rate);
    auto c2 = [rate](float price) -> float { return price * (1 - rate / 100); };

    float p1 = c1(3699); //仿函数
    float p2 = c2(3699); //Lambda函数

    return 0;
}

仿函数以 rate 初始化,Lambda 捕捉 rate 变量,参数传递上,两者一致。

事实上,仿函数就是实现 Lambda 函数一种方式,编译器通常会把 Lambda 函数转换为一个仿函数对象,但是仿函数的语法却给我们带来了很大的便捷。

在 C++11 中,Lambda 函数被广泛使用,很多仿函数被取代。

Lambda函数与static inline函数

Lambda 函数可以省略外部声明的 static inline 函数,其相当于一个局部函数。局部函数仅属于父作用域,

比起外部的 static inline 函数,或者是自定义的宏,Lambda 函数并没有实际运行时的性能优势(但也不会差),但是 Lambda 函数可读性更好。

父函数结束后,该 Lambda 函数就不再可用了,不会污染任何名字空间。

关于值传递捕捉和mutable

上面提到过 mutable 可以取消 Lambda 的常量属性,如果值传递想要在函数域内修改就要加 mutable。

先看一个例子:

int main(int argc, char* argv[])
{
    int j = 12;
    auto by_val = [=] { return j + 1; };
    auto by_ref = [&] { return j + 1; };
    cout << by_val() << endl;   //13
    cout << by_ref() << endl;   //13
    j++;
    cout << by_val() << endl;   //13
    cout << by_ref() << endl;   //14
    return 0;
}

上面的例子,j++ 了之后调用值传递结果依旧是 12,原因是,值传递 j 被视为一个常量,一旦初始化,就不会再修改(可以认为是一个和父作用域中 j 同名的常量),而再引用捕捉中,j 仍然是父作用域中的值。

其实一个值传递的的 Lambda 转换为仿函数后,会成为一个 class 的常量成员函数。

代码基本如下:

class const_val_lambda
{
public:
    const_val_lambda(int v):val(v){}
public:
    void operator()()const { val = 3; } //报错
private:
    int val;
};

但是使用引用的方式不会报错,因为不会改变引用本身,只会改变引用的值。

准确地讲,现有 C++11 标准中的 Lambda 等价的是有常量 operator() 的仿函数。因此在使用捕捉列表的时候必须注意,按值传递方式捕捉的变量是 Lambda 函数中不可更改的常量。标准这么设计可能是源自早期 STL 算法一些设计上的缺陷(对仿函数没有做限制,从而导致一些设计不算特别良好的算法出错)。而更一般地讲,这样的设计有其合理性,改变从上下文中拷贝而来的临时变量通常不具有任何意义。绝大多数时候,临时变量只是用于 Lambda 函数的输入,如果需要输出结果到上下文,我们可以使用引用,或者通过让 Lambda 函数返回值来实现。此外,Lambda 函数的 mutable 修饰符可以消除其常量性,不过这实际上只是提供了一种语法上的可能性,现实中应该没有多少需要使用 mutable 的 lambda 函数的地方。大多数时候,我们使用默认版本的(非mutable)的 Lambda 函数也就足够了。

Lambda函数与函数指针

Lambda 函数并不是简单的函数指针类型,或者自定义类型。每个Lambda函数会产生一个闭包类型的临时对象(右值)。但是 C++11 允许 Lambda 函数向函数指针的转换,前提是:

  1. Lambda没有捕捉任何变量。
  2. 函数指针所示的函数原型,必须和 Lambda 有相同的调用方式。
int main(int argc, char *argv[])
{
    int a = 3, b = 4;

    auto total = [](int x, int y) -> int { return x + y; };
    typedef int (*all)(int x, int y);
    typedef int (*one)(int x);

    all p;
    p = total;
    one q;
    q = total; //报错,参数不一致

    decltype(total) all_1 = total;
    decltype(total) all_2 = p; //报错,指针无法转换为Lambda

    return 0;
}

Lambda与STL

从 C++11 开始,Lambda 被广泛用在 STL 中,比如 foreach。与函数指针比起来,函数指针有巨大的缺陷:

1.函数定义在别处,阅读起来很困难。

2.使用函数指针,很可能导致编译器不对其进行inline优化,循环次数太多时,函数指针和Lambda比起来性能差距太大。函数指针不能应用在一些运行时才能决定的状态,在没有 C++11 时,只能用仿函数。使得学习 STL 算法的代价大大降低。

但是 Lambda 并不是仿函数的完全代替者。由 Lambda 的捕捉列表的限制造成的,仅能捕捉副作用域的变量。仿函数具有天生跨作用域共享的特征。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值