72、Lambda 表达式

Lambda 表达式实际上就是提供了⼀个类似匿名函数的特性,⽽匿名函数则是在需要⼀个函数,但是⼜不想费⼒去命名⼀个函数的情况下去使⽤的。 利⽤ lambda 表达式可以编写内嵌的匿名函数,⽤以替换独⽴函数或者函数对象,并且使代码更可读。

从本质上来讲, lambda 表达式只是⼀种语法糖,因为所有其能完成的⼯作都可以⽤其它稍微复杂的代码来实现,但是它简便的语法却给 C++ 带来了深远的影响。 从⼴义上说,lamdba 表达式产⽣的是函数对象。在类中,可以重载函数调用运算符(),此时类的对象可以将具有类似函数的⾏为,我们称这些对象为函数对象(Function Object)或者仿函数(Functor).相⽐ lambda表达式,函数对象有⾃⼰独特的优势。

lambda 表达式⼀般都是从⽅括号[]开始,然后结束于花括号{},花括号⾥⾯就像定义函数那 样,包含了 lamdba 表达式体,⼀个最简单的例⼦如下:

// 定义简单的lambda表达式

auto basicLambda = [] { cout << "Hello, world!" << endl; };

basicLambda(); // 输出:Hello, world!

上⾯是最简单的 lambda 表达式,没有参数。如果需要参数,那么就要像函数那样,放在圆括 号⾥⾯,如果有返回值,返回类型要放在->后⾯,即拖尾返回类型,当然你也可以忽略返回类型, lambda会帮你⾃动推断出返回类型:

// 指明返回类型,托尾返回类型 

auto add = [](int a, int b) -> int { return a + b; }; 

// ⾃动推断返回类型

 auto multiply = [](int a, int b) { return a * b; };

 int sum = add(2, 5); // 输出:7

 int product = multiply(2, 5); // 输出:10

最前边的 [] 是 lambda 表达式的⼀个很重要的功能,就是闭包。 先说明⼀下 lambda 表达式的⼤致原理:每当你定义⼀个 lambda 表达式后,编译器会⾃动 ⽣成⼀个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。 那么在运⾏时,这个 lambda 表达式就会返回⼀个匿名的闭包实例,其实⼀个右值。所以,我 们上⾯的 lambda 表达式的结果就是⼀个个闭包实例。 闭包的⼀个强⼤之处是其可以通过传值或者引⽤的⽅式捕捉其封装作⽤域内的变量,前⾯的方括号就是⽤来定义捕捉模式以及变量,我们⼜将其称为 lambda 捕捉块。例⼦如下:

int main() {

 int x = 10;  

auto add_x = [x](int a) { return a + x; }; // 复制捕捉x,lambda 表达式⽆法修改此变量

auto multiply_x = [&x](int a) { return a * x; }; // 引⽤捕捉x,lambda 表达式可以修改此变量 

 cout << add_x(10) << " " << multiply_x(10) << endl; // 输出:20 100 

return 0; }

捕获的⽅式可以是引⽤也可以是复制,但是具体说来会有以下⼏种情况来捕获其所在作⽤域中的变量。 []:默认不捕获任何变量; [=]:默认以值捕获所有变量; [&]:默认以引⽤捕获所有变量; [x]:仅以值捕获x,其它变量不捕获; [&x]:仅以引⽤捕获x,其它变量不捕获; [=, &x]:默认以值捕获所有变量,但是x是例外,通过引⽤捕获; [&, x]:默认以引⽤捕获所有变量,但是x是例外,通过值捕获; [this]:通过引⽤捕获当前对象(其实是复制指针); [*this]:通过传值⽅式捕获当前对象;

⽽ lambda 表达式⼀个更重要的应⽤是其可以⽤于函数的参数,通过这种⽅式可以实现回调函数。其实,最常⽤的是在STL算法中,⽐如你要统计⼀个数组中满⾜特定条件的元素数ᰁ, 通过 lambda 表达式给出条件,传递给 count_if 函数:

int val = 3; 
vector v {1, 8, 5, 3, 6, 10};

int count = std::count_if(v.beigin(), v.end(), [val](int x) { return x > val; });
// v中⼤于3的元素数

最后给出 lambda 表达式的完整语法:

[ capture-list ] ( params ) mutable(optional) constexpr(optional)(c++17)
exception attribute -> ret { body }
// 可选的简化语法
[ capture-list ] ( params ) -> ret { body } 
[ capture-list ] ( params ) { body } 
[ capture-list ] { body }

capture-list:捕捉列表,这个不⽤多说,前⾯已经讲过,它不能省略; params:参数列表,可以省略(但是后⾯必须紧跟函数体); mutable:可选,将 lambda 表达式标记为 mutable 后,函数体就可以修改传值⽅式捕 获的变量; constexpr:可选, C++17 ,可以指定 lambda 表达式是⼀个常量函数; exception:可选,指定 lambda 表达式可以抛出的异常; attribute:可选,指定 lambda 表达式的特性; ret:可选,返回值类型; body:函数执⾏体。

Last updated