lambda函数的语法定义如下:
[capture](parameters) lambda修饰符 -> return-type {statement}
解释:
[capture]: 捕捉列表,总是出现在lambda函数的开始处。作为判断代码是否是lambda函数的标志。
(parameters): 参数列表,如无参数,可省略
lambda 修饰符:比如 mutable 修饰符,lambda默认情况下总是一个const函数,mutable可以取消其常量性。可省略。
->return-type: 返回类型,如不需要返回值或返回值类型明确编译器可自行推导时,可省略。
{statement}函数体
lambda函数与普通函数最大的区别在于lambda函数可通过捕捉列表对上下文中的变量进行捕捉。捕捉列表的语法如下:
[var]:表示以值传递的方式捕捉变量var
[=]:表示以值传递的方式捕捉所有父作用域的变量(包括this)
[&var]:表示以引用方式传递捕捉变量var
[&]:表示以引用方式捕捉所有父作用域变量(包括this)
[this]表示值传递方式捕捉当前的this指针
[] 表示不捕获lambda表达式所在作用域内的任何变量,这时如果在lambda函数体内使用了lambda所在作用域中的变量,就会报错,比如下面的例子:
int main()
{
int a = 10;
int b = 20;
auto f = [](int v) {std::cout << a+v << endl; };// 使用了变量a
f(3);
}
上面的代码在vs 2019中会报如下错误:
error C3493: 'a' cannot be implicitly captured because no default capture mode has been specified
捕捉列表也可以是上上述值的组合,例如 [=,&a,&b]表示以引用方式捕捉a和b,并以值传递捕捉父作用域所有其他变量。但要注意的是捕捉列表不能重复捕捉,比如 [=,a] 已经以=表示要捕捉父作用域所有变量了,在捕捉a就重复了。
注意:C++11标准规定,在块作用域以外的lambda函数(即不包含在任何大括号中的lambda),捕捉列表必须为空,这中情况下,lambda函数除语法不同外,它和普通的函数没有多大区别。lambda函数在C++11中默认是内联的(inline)。
lambda函数和仿函数的关系
仿函数也称为“函数对象”,就是重载了()的一种自定义类型对象,如下代码所示, MyFunction就是一个仿函数:
class MyFunction
{
public:
int operator()(int x, int y){return x+y;}
};
int main()
{
int a = 3, b = 4;
MyFunction func;
return func(4,6);
}
仿函数看起来像个函数,实际上它是一个类对象。通过上面的代码,我们可以发现仿函数和lambda函数一样, 都可以捕捉一些变量作为初始状态。实际上,仿函数是编译器实现lambda的一种方式。因此,在C++11中,我们可以将lambda视为仿函数的一种等价关系。
这里推荐一个网站:C++ Insights, 在这个网站上可以查看C++模板推导,类型推导,lambda生成过程,比如下面的lambda表达式
#include <cstdio>
int main()
{
int x = 1;int b = 1;
auto p = [&x, b] (int a, int c)->int {a = x + b; };
p(3,4);
}
在C++ insights中,我们可以看到lambda其实就是一个函数对象:
#include <cstdio>
int main()
{
int x = 1;
int b = 1;
class __lambda_6_13
{
public:
inline /*constexpr */ int operator()(int a, int c) const
{
a = (x + b);
}
private:
int & x;
int b;
public:
__lambda_6_13(int & _x, int & _b)
: x{_x}
, b{_b}
{}
};
__lambda_6_13 p = __lambda_6_13{x, b};
p.operator()(3, 4);
return 0;
}
C++14对lambda的改进
捕获中的标识符可以初始化(lambda init-capture)
int x = 4;
auto y = [&r = x, x = x + 1]()->int
{
r += 2;
return x * x;
}(); // 更新 ::x 到 6 并初始化 y 为 25。
泛型lambda (generic lambda)
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
auto参数也可以是一个形参包
auto printer = [](auto p1, auto p2, auto p3){
std::cout<<p1<<p2<<p3<<std::endl;
}
auto glambda = [](auto printer) {
return [=](auto&&... ts) // generic lambda, ts is a parameter pack
{
printer(std::forward<decltype(ts)>(ts)...);
// nullary lambda (takes no parameters):
return [=] { printer(ts...); };
};
}
auto p = glambda(printer);
auto q = p(1, "abc", 3); // 输出 1abc3
q(); // 输出 1abc3
C++17 对lambda的改进补充
捕获*this
当默认捕获符是 =
时,后继的简单捕获符必须以 &
开始,或者为 *this.
struct S2 { void f(int i); };
void S2::f(int i)
{
[=]{}; // OK:默认以复制捕获
[=, &i]{}; // OK:以复制捕获,但 i 以引用捕获
[=, *this]{}; // C++17 前:错误:无效语法
// C++17 起:OK:以复制捕获外围的 S2
}
增加constexpr lambda修饰符
显式指定函数调用运算符或运算符模板的任意特化为 constexpr 函数。如果没有此说明符但函数调用运算符或任意给定的运算符模板特化恰好满足针对 constexpr 函数的所有要求,那么它也会是 constexpr
的
auto const_lambda = [](int a, int b) constexpr {...}
C++20对lambda的改进或补充
捕获this
当默认捕获符是 =
时,后继的简单捕获符必须以 &
开始,或者为 *this (C++17 起) 或 this (C++20 起)。
struct S2 { void f(int i); };
void S2::f(int i)
{
[=, this] {}; // C++20 前:错误:= 为默认时的 this
// C++20 起:OK:同 [=]
}
捕获不定长实参
template <typename... Args>
auto funs(Args&&... args){
// 捕获值
return [...args = std::forward<Args>(args)] {
};
}
template <typename... Args>
auto funs(Args&&... args){
// 捕获引用
return [&...args = std::forward<Args>(args)] {
};
}
支持模板形参
// 泛型 lambda,operator() 是一个拥有两个(模板)形参的模板
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// 泛型 lambda,operator() 是一个拥有一个形参包的模板
auto f = []<typename... Ts>(Ts&&... ts)
{
return foo(std::forward<Ts>(ts)...);
};
增加consteval lambda修饰符
指定函数调用运算符或任意给定的运算符模板特化为立即函数。不能同时使用 consteval 和 constexpr。
auto ce_lambda = [](int a, int b) consteval -> int {};