借鉴自:《c++ primer》
一个lambda表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数,与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体,但与函数不同,lambda可能定义在函数内部,而且lambda必须使用尾置返回。
lambda表达式形式:
[capture list] (parameter list) opt-> return type {function body}
1. capture list:捕获列表,是一个lambda所在函数中定义的局部变量的列表(通常为空)
2. parameter list:参数列表
3. opt是函数选项,可以填写mutable,exception,attribute(选填写)
mutable:说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法
exception:说明lambda表达式是否抛出异常以及何种异常
attribute:用来声明属性
4.return type:返回类型
5.function body:函数体
我们可以忽略参数列表和返回类型、函数选项(和参数列表共存),但必须永远包含捕获列表和函数体,忽略括号和参数列表等价于指定一个空参数列表,忽略返回类型lambda根据函数体中的代码推断出返回类型,如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。
auto f = [] {return 42; };
cout << f() << endl; //42
lambda表达式和普通函数的区别
1. lambda可能定义在函数内部
2. lambda必须使用尾置返回
3. lambda不能有默认参数(因此,一个lambda调用的实参数目永远与形参数目相等)
捕获列表
1.[]不捕获任何变量。
2.[&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
3.[=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)
4.[=,&foo] [&,foo] 混合捕获
5.[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量(非static),才能在函数体中使用该变量。捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。
//错误:sz未捕获
[](const string& a)
{return a.size() >= sz; };
int i = 100; //全局变量
int main()
{
//int i = 100; 在内部定义i,则不能在lambda中使用i
[](string& s)
{return s.size() >= i; };
}
int main()
{
static int i = 100;
[](string& s)
{return s.size() >= i; }; //可以使用static修饰的i
}
值捕获
值捕获的方式与传值参数类似,采用值捕获的前提是变量可以拷贝,与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝,因此随后对其修改不会影响到lambda内对应的值。
void foo()
{
size_t v1 = 42; //局部变量
//将v1拷贝到名为f的可调用对象
auto f = [v1] {return v1; };
v1 = 0;
auto j = f(); //j为42 f保存了我们创建它时v1的拷贝
}
引用捕获
一个以引用方式捕获的变量与其他任何类型的引用的行为类似,当我们在lambda函数体内使用此变量时,实际上使用的是引用所用绑定的对象,引用捕获与返回引用有着相同的问题和限制,如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的, lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了,如果lambda可能在函数结束后执行,捕获的引用指向的局部变量就已经消失(指针捕获同理)。一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。
void foo()
{
size_t v1 = 42; //局部变量
//对象f2包含v1的引用
auto f = [&v1] {return ++v1; };
cout<< v1 <<endl; //43
v1 = 0;
auto j = f(); //j为1 f保存v1的引用,而非拷贝
}
void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c =' ')
{
//打印count的语句改为打印到os
for_each(words.begin(), words.end(), [&os, c](const string& s) {os << s << c; }); //向函数传递一个lambda
}
我们不能拷贝ostream对象,因此捕获os的唯一方法就是捕获其引用(或指向os的指针)
返回的lambda不能包含引用捕获(悬挂引用)
我们可以向一个函数传递一个lambda,也可以从一个函数返回lambda,如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。因为引用捕获不会延长引用的变量的生命周期。
std::function<int(int)> add_x(int x)
{
return [&](int a) { return x + a; };
}
隐式捕获
除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量,为了指示编译器推断捕获列表,应该捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。
int main()
{
int sz = 10;
auto f = [=](string& s) {return s.size() > sz; };
}
混合捕获(混合使用隐式捕获和显式捕获)
如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获,当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=,此符号指定了默认捕获方式为引用或值。且显式捕获的变量必须使用与隐式捕获不同的方式。
void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c = ' ')
{
//os隐式捕获 c显式捕获
for_each(words.begin(), words.end(), [&, c](const string& s) {os << s << c; });
//os显式捕获,c隐式捕获
for_each(words.begin(), words.end(), [=, &os](const string& s) {os << s << c; }); //向函数传递一个lambda
}
捕获this指针
捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限
class A
{
public:
int _a = 0;
void func(int x, int y)
{
auto f = [] { return _a; }; //错误 没有捕获外部变量
auto f1 = [this] { return _a; };
auto f2 = [this, x, y] { return _a + x + y; };
auto f3 = [this] { return ++_a; };
cout << f3() << endl; //1
}
};
int main()
{
A a;
cout << a._a<< endl; //0
a.func(1, 2);
cout << a._a << endl;//1
}
可变lambda
默认情况下,对于个值被拷贝的变量,lambda不会改变其值,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表上加关键字mutable,
int main()
{
int i = 0;
//f可以改变它所捕获的变量的值
auto f = [=]()mutable {return ++i; };
cout << f() << endl; //1
cout << i << endl; //0
}
原因:lambda表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终会变为闭包类型的成员变量。按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值的。而mutable的作用,就在于取消operator()的const。
指定lambda返回类型(尾置返回)
如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void,与其他void的函数类似,被推断返回void的lambda不能返回值。当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。
int main()
{
transform(vi.begin(), vi.end(), vi.begin(), [](int i) {return i < 0 ? -i : i; });
//错误:不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i) {if (i < 0) return -i; else return i; });
//修改为尾置返回
transform(vi.begin(), vi.end(), vi.begin(), [](int i)->int {if (i < 0) return -i; else return i; });
}
lambda表达式的大致原理
以下转载自:https://blog.csdn.net/qq_43313035/article/details/98533582
- 每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。这个类不含默认构造函数、赋值运算符及默认析构函数,它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定。
- 那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。
- 所以,我们上面的lambda表达式的结果就是一个个闭包。
- 对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。
- 对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。
- 至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关
- lambda表达式是不能被赋值的,闭包类型禁用了赋值操作符,但是没有禁用复制构造函数,所以你仍然可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };
a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本