【C++11】lambda表达式(匿名函数)

文章目录

  • [1. lambda表达式的语法](#1. lambda表达式的语法)
  • [2. 举例](#2. 举例)
  • [3. lambda的应用](#3. lambda的应用)
  • [4. lambda捕捉列表](#4. lambda捕捉列表)
    • [4.1 传值捕捉与传引用捕捉](#4.1 传值捕捉与传引用捕捉)
    • [4.2 隐式捕捉与显式捕捉](#4.2 隐式捕捉与显式捕捉)
    • [4.3 混合捕捉](#4.3 混合捕捉)
    • [4.4 全局变量和静态变量的捕捉](#4.4 全局变量和静态变量的捕捉)
    • [4.5 mutable](#4.5 mutable)
  • [5. lambda的原理](#5. lambda的原理)

1. lambda表达式的语法

lambda 表达式本质是⼀个匿名函数对象 ,跟普通函数不同的是他可以定义在函数内部(而普通函数通常是定义在全局的)。
lambda 表达式在语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收lambda对象。

lambda表达式的格式:

[capture-list] (parameters)-> return type { function boby }

分别代表什么意思呢?

[capture-list] : 捕捉列表
该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使用,捕捉列表可以传值或传引用捕捉,具体细节我们后面再细讲。捕捉列表为空也不能省略。
(parameters) :参数列表
与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略
->return type :返回值类型
用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
{function boby} :函数体
函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。

与我们之前学过的普通函数相比:

相信只有捕捉列表我们是比较陌生的,其它几个东西我们都明白它是什么意思。
另外我们发现它是没有函数名的,这也是它为什么叫做匿名函数的原因。

2. 举例

那下面就带大家来简单写一个lambda表达式,练习一下。

比如:


这里赋值符号右边的就是一个lambda表达式,或者说一个匿名函数对象。
我们用一个auto定义的变量add来接受这个匿名函数,编译器会自动推导它的类型。
当然我们也可以调用这个匿名函数


那么最开始的[]就是捕捉列表,我们后面会详细讲解。

但是要注意:

  1. 捕捉列表为空也不能省略
    就像上面的例子,没有捕捉任何东西,但是[]是不能省略的。
  2. 返回值类型可以省略
    编译器可以通过返回对象自动推导
  3. 参数为空则参数列表可以省略
    比如我们这个匿名函数只是完成一个字符串的打印,不需要参数

    这里我们参数列表和返回值都省略了
    注意这种情况:

    如果写了返回值类型,但是参数列表为空时,至少要写()
  4. 函数体不能省略

再比如,写一个交换两个变量的匿名函数对象

3. lambda的应用

在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象

函数指针的定义和书写比较复杂麻烦,仿函数又要定义⼀个类,也相对比较麻烦。某些场景下使用 lambda 去定义可调用对象,既简单又方便。

比如,我们来看这样一个场景:


现在有一个描述某种商品的结构体。定义了一个结构体数组,现在我们要对这组商品进行排序
我们要想直接调用sort对这个vector进行排序肯定是不行的,因为里面存储的元素是自定义类型的Goods变量

当然我们可以针对Goods类对>或<操作符进行重载,实现我们想要的比较规则,比如按价格或评价升序降序去排。

比如,我们按价格比较升序

当然,我们也可以传函数指针或仿函数


比如这里也按价格升序,那这里大家仿函数的命名就建议最好有意义一些,如果你写成一个compare1(可读性很差)

那我只看代码,就不好判断出来你是按什么排序,升序还是降序,还要点开对应的仿函数看它的实现逻辑

但是,这种场景:

如果比较的逻辑比较简单,那这里的可调用对象我传一个lambda表达式过去就会非常简洁清晰。
sort的第三个模板参数,我们直接传一个lambda表达式就可以了

当然,上面只是简单的列举了一类lambda的使用场景, 在很多其他地方lambda用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的,以后我们会不断接触到。

4. lambda捕捉列表

那下面我们来重点学习一下lambda的捕捉列表

lambda 表达式中默认只能用lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉

比如:


在这个lambda表达式里面,我们想使用变量a和b是不行的,因为变量a和b在lambda的外层作用域中。要想访问,我们就需要进行捕捉。

捕捉呢又具体分为:

传值捕捉、传引用捕捉、隐式捕捉、显式捕捉

4.1 传值捕捉与传引用捕捉

传值捕捉(直接在捕捉列表中写对应变量名)的变量不能修改,传引用捕捉(捕捉列表中写&变量名)的变量可以修改,并且修改会影响外层被捕捉变量。
捕捉的多个变量用逗号分割。
如:[x,y, &z] 表示x和y值捕捉,z引用捕捉。
当然,全局对象即使不捕捉也可以直接用

比如,在上面的例子中


对变量a进行值捕捉,b进行引用捕捉
然后就可以成功访问了。
b是引用捕捉可以修改

a是值捕捉不可修改

4.2 隐式捕捉与显式捕捉

上面的例子中我们显式指定了我们要捕捉的变量,叫做显式捕捉。

除了显式捕捉,还有隐式捕捉

隐式捕捉同样分为值捕捉和引用捕捉
在捕捉列表写⼀个=表示隐式值捕捉,这样我们在lambda中用到哪些变量,就会自动值捕捉这些变量

当然值捕捉就不能修改它们。
在捕捉列表写⼀个&表示隐式引用捕捉,这样我们 lambda 表达式中用了哪些变量,编译器就会自动引用捕捉那些变量,既然是引用捕捉那就可以修改

4.3 混合捕捉

第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显式捕捉。

=, \&x\]表示其他变量隐式值捕捉,x引用捕捉; \[\&, x, y\]表示其他变量引用捕捉,x和y值捕捉。 当使用混合捕捉时,第⼀个元素必须是`&`或`=`,并且\&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

比如:

a,b引用捕捉,其余用到哪个变量就值捕捉哪个变量
但要注意,这样写的话,一定要把=写到前面

a,b值捕捉,其余引用捕捉

4.4 全局变量和静态变量的捕捉

lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量全局变量 ,静态局部变量和全局变量也不需要捕捉 , lambda 表达式中可以直接使用
这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。(当然lambda我们一般不会写到全局)

比如:


4.5 mutable

传值捕捉本质是一种拷贝,并且被const修饰(所以值捕捉的变量不能修改)
mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是其拷贝,不会影响原始被捕捉对象。
使用该修饰符后,参数列表不可省略(即使参数为空)。

mutable相当于去掉const属性,可以修改了,但是修改了不会影响外面被捕捉的值

5. lambda的原理

lambda 的原理和范围for很像

编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。
范围for底层是迭代器,而lambda底层是仿函数对象 ,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。
仿函数的类名是编译器按⼀定规则生成的,保证不同的 lambda 生成的类名不同,lambda表达式的参数/返
回类型/函数体就是对应仿函数operator()的参数/返回类型/函数体。
lambda 的捕捉列表本质是对应仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看用到哪些就传那些对象。

上面的原理,我们可以透过汇编层了解⼀下

现在这里有一个仿函数

然后再来写一个lambda

lambda和仿函数的参数列表和函数体一样,lambda捕捉列表的变量和仿函数的成员变量一样。

观察一些它们底层的汇编

我们可以看到,lambda底层也是去调用了operator()

相关推荐
陳10302 小时前
C++:vector(2)
开发语言·c++
猴子年华、2 小时前
【每日一技】:SQL 常用函数实战速查表(函数 + 场景版)
java·数据库·sql·mysql
盖世灬英雄z2 小时前
数据结构与算法学习(一)
c++·学习·排序算法
码农水水2 小时前
京东Java面试被问:系统限流的实现方式
java·开发语言·面试
CodeOfCC2 小时前
C++ 基于kmp解析nalu
c++·音视频·实时音视频·h.265·h.264
Sheep Shaun2 小时前
STL中的map和set:红黑树的优雅应用
开发语言·数据结构·c++·后端·c#
宁晓3 小时前
单表配置多级类型,按名称模糊筛选
java·后端
Yu_iChan3 小时前
Day03 公共字段填充与菜品管理
java·开发语言
独自破碎E3 小时前
如何防止接口被恶意刷量?
java·开发语言