目录
一、语法格式
二、函数对象和 lambda 表达式
一、语法格式
定义一个 lambda 表达式(lambda 函数)的语法格式如下:
[capture-list] (parameters) mutable noexcept/throw() -> return-type { statements };
即
[捕获列表] (参数列表) 可变规则 异常说明 -> 返回类型 { 函数体 };
其中各部分的含义分别为:
-
[捕获列表]:[] 是 lambda 引出符,编译器根据该引出符判断接下来的代码是否为 lambda 函数,所以其不能被省略。捕获列表能够捕获外部变量(即和当前 lambda 函数位于同一作用域内的所有局部变量)以供 lambda 函数使用。
捕获列表可由多个捕获项组成,并以逗号分割。捕获列表有以下几种形式:
[捕获列表] 说明 [] 不捕获任何外部变量 [=] 以值传递的方式捕获所有外部变量(包括 this) [&] 以引用传递的方式捕获所有外部变量(包括 this) [val1, val2, ...] 以值传递的方式捕获 val1、val2 等指定的外部变量 [&val1, &val2, ...] 以引用传递的方式捕获 val1、val2 等指定的外部变量 [=, &val1, ...] 以引用传递的方式捕获 val1 等指定的外部变量,以值传递的方式捕获其他所有外部变量 [&, val1, ...] 以值传递的方式捕获 val1 等指定的外部变量,以引用传递的方式捕获其他所有外部变量 [this] 以值传递的方式捕获当前的 this 指针 注意:捕获列表不允许外部变量重复传递,例如 [=, val1]、[&、&val1]。
-
(参数列表):和普通函数不同的是,如果不需要传递参数,可以连同 () 一起省略。
-
可变规则:mutable 关键字可以省略,如果要使用,则之前的 () 将不能省略(参数个数可以为 0)。默认情况下,以值传递方式捕获的外部变量,不允许在 lambda 函数内部修改它们的值(可以理解为这部分变量都是 const 变量),如果想修改它们,就必须使用 mutable 关键字。
注意:修改以值传递方式捕获的外部变量,实则修改的是外部变量的拷贝,而不是真正的外部变量。
-
异常说明:可以省略,如果要使用,则之前的 () 将不能省略(参数个数可以为 0)。默认情况下,lambda 函数的函数体中可以抛出任何类型的异常,标注 noexcept 关键字,则表示函数体内不会抛出任何异常;使用 throw() 则可以指定 lambda 函数内部可以抛出的异常类型。
-
-> 返回类型:返回类型为 void,可以省略;返回类型明确的情况下,也可以省略,由编译器对返回类型进行推导。
-
{ 函数体 }:函数体内部除了可以使用指定传递进来的参数,还可以使用所有捕获的外部变量以及全局范围内的所有全局变量。
示例一:
#include <iostream>
using namespace std;
class A
{
public:
A(int x = 0) : _i(x) { }
void sayHello() const { cout << "Hello" << endl; }
void test()
{
// 以值传递的方式捕获当前的 this 指针
auto func = [this] {
this->_i = 10;
cout << this->_i << endl;
this->sayHello();
// 函数体中 this 可以省略
};
func();
}
private:
int _i;
};
int main()
{
// 最简单的 lambda 表达式,该表达式没有任何意义
[] {};
A a;
a.test();
// 10
// Hello
return 0;
}
示例二:
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
// 商品类
struct Goods
{
string _name; // 名字
double _price; // 价格
int _eval; // 评价
Goods(const char* str, double price, int eval)
: _name(str), _price(price), _eval(eval)
{ }
};
struct CmpByPriceLess
{
bool operator()(const Goods& lhs, const Goods& rhs)
{
return lhs._price < rhs._price;
}
};
struct CmpByPriceGreater
{
bool operator()(const Goods& lhs, const Goods& rhs)
{
return lhs._price > rhs._price;
}
};
int main()
{
vector<Goods> v = {
{ "苹果", 2.1, 5 },
{ "香蕉", 3.0, 4 },
{ "橙子", 2.2, 3 },
{ "菠萝", 1.5, 4}
};
// 按价格排序方法一:
sort(v.begin(), v.end(), CmpByPriceLess()); // 升序
sort(v.begin(), v.end(), CmpByPriceGreater()); // 降序
// 按价格排序方法二:
sort(v.begin(), v.end(), [](const Goods& lhs, const Goods& rhs) {
return lhs._price < rhs._price; }); // 升序
sort(v.begin(), v.end(), [](const Goods& lhs, const Goods& rhs) {
return lhs._price > rhs._price; }); // 降序
return 0;
}
二、函数对象和 lambda 表达式
// 利率类
class Rate
{
public:
Rate(double rate) : _rate(rate) { }
double operator()(double money, int year)
{
return money * _rate * year; // 利息 = 本金 * 利率 * 存期
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// 函数对象
Rate r1(rate);
r1(1000, 2);
// lambda 表达式
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
r2(1000, 2);
return 0;
}
所以实际上在底层中,编译器对一个 lambda 表达式的处理方式,完全就是按照函数对象的方式来处理的,即如果定义了一个 lambda 表达式,编译器会自动生成一个类,该类中重载了 operator()。
注意:lambda 表达式即使看起来是一样,但它们的类型实际上是不相同的。
#include <iostream>
using namespace std;
int main()
{
auto add1 = [](int x, int y) { return x + y; };
auto add2 = [](int x, int y) { return x + y; };
cout << typeid(add1).name() << endl;
// class <lambda_2af21e002ff22901d6b0fa11901f235e>
cout << typeid(add2).name() << endl;
// class <lambda_751ae2273733b2c2d261a415ac8d186d>
return 0;
}