[C++11]Lambda表达式

lambda函数是C++11标准新增的语法糖,也称为lambda表达式或匿名函数

lambda函数的特点是:距离近、简洁、高效和功能强大。

示例: [] ( const int& no ) -> void { cout << "我是第" << no << "名。\n"; };

语法:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//函数
void show1(const int& no){
    cout<<"我给"<<no<<"号来一拳。\n";
} 
//仿函数
class show2{
public:
    void operator()(const int& no){
        cout<<"我也给"<<no<<"号来一拳。\n";
    }
}; 

int main(){
    vector<int> vv={4,3,5};//存放挨打者编号的容器
    //第三个参数是普通函数 
    for_each(vv.begin(),vv.end(),show1);
    //第三个参数是仿函数 
    for_each(vv.begin(),vv.end(),show2());
    //第三个参数是LAMBDA表达式。
    /*for_each(vv.begin(),vv.end(),
        [](const int& no){
        cout<<"我还给"<<no<<"号来一拳。\n";
        }
    ); */


    auto f=[](const int& no)->double{
        cout<<"我还给"<<no<<"号来一拳。\n";
        return 1.5;
        };
    for_each(vv.begin(),vv.end(),f);
    //也可以像调用普通函数一样,调用Lambda表达式 
    f(222);
} 

一、参数列表

参数列表是可选的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写

与普通函数的不同:

  • lambda函数不能有默认参数

  • 所有参数必须有参数名

  • 不支持可变参数

二、返回类型

    用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。

    如果有返回类型,建议显式的指定,自动推断可能与预期不一致。

三、函数体

    类似于普通函数的函数体

四、捕获列表

    通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。

    捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。

    以下列出了不同的捕获列表的方式。

1)值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。

与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。

例如:

 size_t v1 =42;
 auto f = [v1]  { return v1; }; // 使用了值捕获,将v1拷贝到名为f的可调用对象。
 v1 = 0;
 auto j = f();    // j为42,f保存了我们创建它是v1的拷贝。

由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。

默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。

2)引用捕获

和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。

size_t v1 =42;
auto f = [&v1]{ return v1; };// 引用捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f();    // j为0。

如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。

3)隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

int a = 123;
auto f = [=] { cout << a << endl; };       //值捕获
f();       // 输出:123
auto f1 = [&] { cout << a++ << endl; };       //引用捕获
f1();     //输出:123(采用了后++)
cout << a<< endl;          //输出 124

4)混合方式捕获

lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。

混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。

需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:

int a = 123;
auto f = [=] { cout << a << endl; };       //值捕获
f();       // 输出:123
auto f1 = [&] { cout << a++ << endl; };       //引用捕获
f1();     //输出:123(采用了后++)
cout << a<< endl;          //输出 124

5)修改值捕获变量的值

在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。

在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。

int a = 123;
auto f =[a]()mutable { cout << ++a << endl; }; // 不会报错
cout << a<< endl;     // 输出:123
f();                  // 输出:124
cout << a<< endl;     // 输出:123

6)异常说明

lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。

五、lambda函数本质

当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。

1)采用值捕获

采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。

int a =10;
int b = 20;
auto addfun = [=] (const int c ) -> int { return a+c; };
int c = addfun(b);    
cout << c << endl;
等同于:
class Myclass
{
    int m_a;        // 该成员变量对应通过值捕获的变量。
public:
    Myclass( int a ) : m_a(a){};    // 该形参对应捕获的变量。
    // 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。
    int operator()(const int c) const
    {
        return a + c;
    }
};

默认情况下,有lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。

2)采用引用捕获

如果lambda函数采用引用捕获的方式,编译器直接引用就行了。

唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。

六、Lambda表达式底层原理

实际就是编译器在底层对于Lambda表达式的处理方式,完全就是按照函数对象的方式处理的,就是对()进行了重载:

class Rate
{
private:
public:
    Rate(double rate)
        :_rate(rate)
    {}

    double operator()(double money, int year)
    {
        return money * _rate * year;
    }
    double _rate;
};

int main()
{
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);

    auto r2 = [=](double money, int year)->
            double {return money * rate * year; };
    r2(10000, 2);
    return 0;
}

当创建Rate对象的时候是构造函数,使用Rate对象的时候就是会调用 Rate 类的 operator() 运算符重载函数;

而Lambda 表达式实际也是这样子的:会调用 < lambda_uuid > 类的构造函数,在使用Rate r2对象时,会调用< lambda_uuid >类的 operator()运算符重载函数。

lambda表达式和范围for是类似的,它们在语法层面上看起来都很神奇,但实际范围for底层就是通过迭代器实现的,lambda表达式底层的处理方式和函数对象是一样的。我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对operator () 运算符进行重载.

注意:lambda表达式之间不能赋值,每个lambda表达式的类型都是不同的(在VS下,lambda表达式会被处理为函数对象,该函数对象对应的类名叫做< lambda_uuid >。),这也是lambda表达式之间不能相互赋值的原因

int main()
{
    int x = 10, y = 20;
    auto Swap1 = [&x,&y]()
    {
        int tmp = x;
        x = y;
        y = tmp;
    };
    auto Swap2 = [&x,&y]()
    {
        int tmp = x;
        x = y;
        y = tmp;
    };
    cout << typeid(Swap1).name() << endl;
    cout << typeid(Swap2).name() << endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值