3.1 Lambda 表达式
Lambda 表达式提供了一个类似于匿名函数的特性。
匿名函数是在一个需要函数,但是又不想费力去命名一个函数的情况下去使用的。这样的场景其实在现代编程语言中有很多,所以匿名函数几乎是现代编程语言的标配。
基础
Lambda 表达式基本语法如下:
[捕获列表](参数列表)mutable(可选) 异常属性 -> 返回类型
{
//函数体
}
上面的语法规则都很好理解,可能[捕获列表]困难一点。
Lambda 表达式内部函数体在默认情况下是不能使用函数体外部的变量的。通过捕获列表,可以起到传递外部数据的作用。根据传递的行为,有以下几种:
1. 值捕获
与参数按照值传递类似,值捕获的前提是要捕获的对象可以拷贝。和值传递不同的是,不捕获的变量在 Lambda 表达式被创建时拷贝,而不是被调用时拷贝。如下代码:
void lambda_value_capture()
{
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value; // stored_value == 1
//因为 copy_value 在创建时就拷贝了一份 value
}
2. 引用捕获
与引用传参类似,引用捕获保存的是引用。如果修改引用,原来的值会发生变化。
void lambda_reference_capture()
{
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value(); //stored_value == 100
//因为 copy_value 保存的是引用
}
3. 隐式捕获
手动写捕获列表有时候很麻烦,可以把这种机械性的工作交给编译器。可以在捕获列表中写一个 & 或 = 向编译器声明采用的捕获方式。
小总结
常用的四种捕获方式:
1.[] 空捕获列表
2.[name1, name2, …] 捕获一系列的变量
3.[&] 引用捕获,让编译器自行推导
4.[=] 值捕获,让编译器自行推导
4.表达式捕获
这里涉及到后面的右值引用以及智能指针。
上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,这些变量均为左值。
C++14 允许捕获的成员用任意的表达式进行初始化,这就允许了右值的捕获。被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 auto 本质上是相同的(需要提供足够的信息,可以让编译器推导出类型)。如下代码:
#include <iostream>
#include <memory> //make_unique
#include <utility> //move
using namespace std;
void lambda_expression_capture()
{
auto important = make_unique<int>(1);
auto add = [v1 = 1, v2 = move(important)](int x, int y) -> int
{
return x + y + v1 + (*v2);
};
cout << add(3, 4) << endl; //输出 9
}
上面的代码中,important 是一个独占指针,不能被值捕获,我们将其转为右值,在表达式中进行初始化。
泛型 Lambda
在 C++11 中,auto 关键字不能出现在参数列表里。从 C++20 起,auto 关键字可以出现在参数列表中。
从 C++14 起,Lambda 函数的形参列表就可以使用 auto 关键字来进行类型推导。如下代码:
auto add = [](auto x, auto y)
{
return x + y;
};
cout << add(1, 2) << endl; //合法,结果为 3
cout << add(1.1, 2.2) << endl; //合法,结果为 3.3
总结
在实际开发中,Lambda 表达上从来不像上面例子中那样使用。文章中只是为了讲解,省去了其他复杂的代码,进行了不太合理的操作。
一开始就说了,Lambda 表达式提供了一种类似于匿名函数的性质。上面的所有代码,全部都背离了这个初衷。
比如 add 这个函数,已经不算是一个匿名函数了。add 就是函数名称,后面也可以通过这个函数名称调用该函数。这样,我直接定义一个真正的函数多好,而且能够大幅度提升可读性。
实际开发中,Lambda 表达式往往配合容器、算法等 STL 相关的东西使用。
大家学习 STL 的时候,一定知道一些容器、算法可以自定义规则。这个自定义的规则通过仿函数实现。
Lambda 表达式天生适配仿函数。所以,Lambda 表达式大多数时候都是作为仿函数去使用。
在虚幻四引擎中,虚幻的 TMap、TArray等容器,以及 Sort、Find 等算法,都可以使用 Lambda 表达式。