Lambda表达式
什么是 Lambda表达式
Lambda表达式是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。Lambda表达式的基本语法如下:
//[capture list] (parameter list) -> return type { function body }
auto func = []() -> int { return 42; };
这里的“-> int”是后置返回类型,指定了 Lambda 表达式的返回类型为 int。
在c++的官方文档中,给出了lamda表达式的四种写法,所以在官方文档上截了一个图。
其中:
capture list 是捕获列表,用于指定 Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。捕获列表可以为空,表示不访问任何外部变量,也可以使用默认捕获模式 & 或 = 来表示按引用或按值捕获所有外部变量,还可以混合使用具体的变量名和默认捕获模式来指定不同的捕获方式。
parameter list 是参数列表,用于表示 Lambda表达式的参数,可以为空,表示没有参数,也可以和普通函数一样指定参数的类型和名称,还可以在 c++14 中使用 auto 关键字来实现泛型参数。
return type 是返回值类型,用于指定 Lambda表达式的返回值类型,可以省略,表示由编译器根据函数体推导,也可以使用 -> 符号显式指定,还可以在 c++14 中使用 auto 关键字来实现泛型返回值。
function body 是函数体,用于表示 Lambda表达式的具体逻辑,可以是一条语句,也可以是多条语句,还可以在 c++14 中使用 constexpr 来实现编译期计算。
捕获方式 | 说明 |
---|---|
[] | 不捕获任何变量。 |
[&] | 捕获外部作用域中的所有变量,并作为引用在函数体中使用(按引用捕获)。 |
[=] | 捕获外部作用域中的所有变量,并作为副本在函数体中使用(按值捕获)。 |
[=,&foo] | 按值捕获外部作用域中的所有变量,并按引用捕获 foo 变量。 |
[a, &b] | 以值的方式捕获 a,以引用的方式捕获 b,也可以捕获多个。 |
[bar] | 按值捕获 bar 变量,同时不捕获其他变量。 |
[this] | 捕获当前类中的 this 指针,让 Lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 Lambda 中使用当前类的成员函数和成员变量。 |
Lambda表达式的捕获方式
值捕获(capture by value):在捕获列表中使用变量名,表示将该变量的值拷贝到 Lambda 表达式中,作为一个数据成员。值捕获的变量在 Lambda 表达式定义时就已经确定,不会随着外部变量的变化而变化。值捕获的变量默认不能在 Lambda 表达式中修改,除非使用 mutable 关键字。例如:
int x = 10;
auto f = [&x] (int y) -> int { return x + y; }; // 引用捕获 x
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 25,受外部 x 的影响
引用捕获(capture by reference):在捕获列表中使用 & 加变量名,表示将该变量的引用传递到 Lambda 表达式中,作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定,会随着外部变量的变化而变化。引用捕获的变量可以在 Lambda 表达式中修改,但要注意生命周期的问题,避免悬空引用的出现。例如:
int x = 10;
auto f = [x] (int y) -> int { return x + y; }; // 值捕获 x
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 15,不受外部 x 的影响
隐式捕获(implicit capture):在捕获列表中使用 =
或 &
,表示按值或按引用捕获 Lambda 表达式中使用的所有外部变量。这种方式可以简化捕获列表的书写,避免过长或遗漏。隐式捕获可以和显式捕获混合使用,但不能和同类型的显式捕获一起使用。例如:
int x = 10;
int y = 20;
auto f = [=, &y] (int z) -> int { return x + y + z; }; // 隐式按值捕获 x,显式按引用捕获 y
x = 30; // 修改外部的 x
y = 40; // 修改外部的 y
cout << f(5) << endl; // 输出 55,不受外部 x 的影响,受外部 y 的影响
初始化捕获(init capture):C++14 引入的一种新的捕获方式,它允许在捕获列表中使用初始化表达式,从而在捕获列表中创建并初始化一个新的变量,而不是捕获一个已存在的变量。这种方式可以使用 auto 关键字来推导类型,也可以显式指定类型。这种方式可以用来捕获只移动的变量,或者捕获 this 指针的值。例如:
int x = 10;
auto f = [z = x + 5] (int y) -> int { return z + y; }; // 初始化捕获 z,相当于值捕获 x + 5
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 20,不受外部 x 的影响
Lambda表达式的优点
Lambda表达式相比于普通函数和普通类,有以下几个优点:
简洁:Lambda表达式可以省略函数名和类名,直接定义和使用,使得代码更加简洁和清晰。
灵活:Lambda表达式可以捕获外部变量,可以作为函数参数,也可以作为函数返回值,使得代码更加灵活和方便。
安全:Lambda表达式可以控制外部变量的访问方式,可以避免全局变量的定义,可以避免悬空指针和无效引用的产生,使得代码更加安全和稳定。
总结
Lambda表达式是 c++11 引入的一个语法,它可以用来定义并创建匿名的函数对象,主要用于方便编程,避免全局变量的定义,并且变量安全。Lambda表达式的语法类似于一个函数定义,但它不需要函数名,可以直接定义并使用。Lambda表达式相比于普通函数和普通类,有以下几个优点:简洁、灵活和安全。Lambda表达式本质上是一个匿名类的对象,因此它可以赋值给一个函数指针或函数引用,也可以作为模板参数传递给一个泛型函数或类。C++14 和 C++17 对 Lambda表达式进行了一些扩展和改进,使得 Lambda表达式更加强大和灵活,主要有以下几个方面:泛型 Lambda、初始化捕获和捕获 this 指针。