一、基本语法

lambda表达式是C++11最重要也是最常用的特性之一,这是现代编程语言的一个特点,lambda表达式有如下的一些优点:

  • 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
  • 简洁:避免了代码膨胀和功能分散,让开发更加高效。
  • 在需要的时间和地点实现功能闭包,使程序更加灵活。

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:

[capture-list](parameters) opt -> return type {function boby;};

​ 其中capture-list是捕获列表,parameters是参数列表,opt是函数选项,return type是返回值类型,function boby是函数体。

  1. [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节在第二部分我们再细讲。捕捉列表为空也不能省略。
  2. (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略。
   auto f = [](){return 1;}	// 没有参数, 参数列表为空
   auto f = []{return 1;}		// 没有参数, 参数列表省略不写
  1. opt :函数选项, 不需要可以省略。
  • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
  • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
  1. return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  2. function body:函数体,函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,这部分不能省略,但函数体可以为空。

下面是一个简单的lambda的示例代码:

int main()
{
	// ⼀个简单的lambda表达式
	auto add1 = [](int x, int y)->int {return x + y; };
	cout << add1(1, 2) << endl;       //输出3
	// 1、捕捉为空也不能省略
	// 2、参数为空可以省略
	// 3、返回值可以省略,可以通过返回对象⾃动推导
	// 4、函数题不能省略
	auto func1 = []
	{
	cout << "hello world" << endl;  //输出hello world
	return 0;
	};
	func1();
	int a = 0, b = 1;
	auto swap1 = [](int& x, int& y)
	{
	int tmp = x;
	x = y;
	y = tmp;
	};
	swap1(a, b);
 	cout << a << ":" << b << endl;    //输出 1:0
	return 0;
}

二、捕获列表

​ lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想用外层作用域中的变量就

需要进行捕捉。具体使用方法如下:

  1. [] - 不捕捉任何变量
  2. [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
  3. [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
    • 拷贝的副本在匿名函数体内部是只读的
  4. [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
  5. [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
  6. [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
  7. [this] - 捕获当前类中的this指针
    • 让lambda表达式拥有和当前类成员函数同样的访问权限
    • 如果已经使用了 & 或者 =, 默认添加此选
  8. lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态
    局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使
    用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
  9. 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
  10. 在捕捉列表中混合使用按值捕捉和引用捕捉。[=, &x]表示其他变量按值捕捉,x引用捕捉;[&, x, y]表示其他变量引用捕捉,x和y按值捕捉。当使用混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

下面是一个示例代码:

#include <iostream>
#include <functional>
using namespace std;
int x = 0;
// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []()
{
	x++;
};

class Test
{
public:
    void output(int x, int y)
    {
        auto x1 = [] {return m_number; };                      // error
        auto x2 = [=] {return m_number + x + y; };             // ok
        auto x3 = [&] {return m_number + x + y; };             // ok
        auto x4 = [this] {return m_number; };                  // ok
        auto x5 = [this] {return m_number + x + y; };          // error
        auto x6 = [this, x, y] {return m_number + x + y; };    // ok
        auto x7 = [this] {return m_number++; };                // ok
    }
    int m_number = 100;
};
int main()
{
    int a=0,b=1;
    auto func=[a,&b]
    {
        b++;
        int ret=a+b;  // 值捕捉的变量不能修改,引用捕捉的变量可以修改
        return ret;
    }
    return 0;
}
  • x1:错误,没有捕获外部变量,不能使用类成员 m_number
  • x2:正确,以值拷贝的方式捕获所有外部变量
  • x3:正确,以引用的方式捕获所有外部变量
  • x4:正确,捕获this指针,可访问对象内部成员
  • x5:错误,捕获this指针,可访问类内部成员,没有捕获到变量x,y,因此不能访问。
  • x6:正确,捕获this指针,x,y
  • x7:正确,捕获this指针,并且可以修改对象内部变量的值

三、返回值

很多时候,lambda表达式的返回值是非常明显的,因此在C++11中允许省略lambda表达式的返回值。

// 完整的lambda表达式定义
auto f = [](int a) -> int
{
    return a+10;  
};

// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
    return a+10;  
};

一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出返回值类型。

// ok,可以自动推导出返回值类型
auto f = [](int i)
{
    return i;
}

// error,不能推导出返回值类型
auto f1 = []()
{
    return {1, 2};	// 基于列表初始化推导返回值,错误
}

四、函数本质

我们来剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:

  • lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
  • 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。

mutable选项的作用就在于取消operator()的const属性。因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

五、Lambda的应用

  1. 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义⼀个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
  2. lambda 在很多其他地⽅用起来也很好⽤。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

struct Goods
{
    string _name;    // 名字
    double _price;   // 价格
    int _evaluate;   // 评价

    // 构造函数
    Goods(const char* str, double price, int evaluate)
        : _name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};

// 仿函数:按价格升序比较
struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    }
};

// 仿函数:按价格降序比较
struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};

// 辅助函数:打印商品列表
void PrintGoods(const vector<Goods>& v)
{
    for (const auto& goods : v)
    {
        cout << "名称:" << goods._name 
             << " 价格:" << goods._price 
             << " 评价:" << goods._evaluate << endl;
    }
    cout << "-------------------------" << endl;
}

int main()
{
    vector<Goods> v = { 
        { "苹果", 2.1, 5 }, 
        { "香蕉", 3, 4 }, 
        { "橙子", 2.2, 3 }, 
        { "菠萝", 1.5, 4 } 
    };

    cout << "原始商品列表:" << endl;
    PrintGoods(v);

    // 1. 用仿函数按价格升序排序
    sort(v.begin(), v.end(), ComparePriceLess());
    cout << "按价格升序(仿函数):" << endl;
    PrintGoods(v);

    // 2. 用仿函数按价格降序排序
    sort(v.begin(), v.end(), ComparePriceGreater());
    cout << "按价格降序(仿函数):" << endl;
    PrintGoods(v);

    // 3. 用lambda按价格升序排序(替代ComparePriceLess)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price;
    });
    cout << "按价格升序(lambda):" << endl;
    PrintGoods(v);

    // 4. 用lambda按价格降序排序(替代ComparePriceGreater)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;
    });
    cout << "按价格降序(lambda):" << endl;
    PrintGoods(v);

    // 5. 用lambda按评价升序排序(无需额外写仿函数)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate < g2._evaluate;
    });
    cout << "按评价升序(lambda):" << endl;
    PrintGoods(v);

    // 6. 用lambda按评价降序排序(无需额外写仿函数)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate > g2._evaluate;
    });
    cout << "按评价降序(lambda):" << endl;
    PrintGoods(v);

    return 0;
}

更多推荐