🎁个人主页:工藤新一¹
🔍系列专栏:C++面向对象(类和对象篇)
🌟心中的天空之城,终会照亮我前方的路
🎉欢迎大家点赞👍评论📝收藏⭐文章
Lambda
一、Lambda表达式语法
lambda
表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda
表达式语法使用层而言没有类型,所以我们⼀般是⽤ auto
或者模板参数定义的对象去接收 lambda
对象
lambda
表达式特点:轻量级- 快速定义一个匿名函数对象(也被称作:CLosure 闭包)
lambda
表达式的格式:[capatrue-list](parameters)-> return type { function body }
-
[capatrue-list]:
捕捉列表,该列表总是出现在[lambda]
函数的开始位置,编译器根据[]
来判断接下来的代码是否为lambda
函数,捕捉列表能够捕捉上下文中的变量 供lambda
函数使用,捕捉列表可以传值和传引用捕捉(注意:即使捕捉列表为空也不能被省略)
-
(parameters):
参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,那么即可连同()
一起省略 -
-> return type:
返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确的情况下,也可省略,由编译器对返回类型进行推导 -
{ function body }:
函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了使用其参数外,还可使用所有捕获到的变量,函数体为空时也不能被省略。
二、Lambda 表达式的应用
在学习 lambda
表达式之前,我们的使⽤的可调⽤对象只有 函数指针 和 仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使用 lambda
去定义可调⽤对象,既简单又方便
lambda
在很多其他地⽅⽤起来也很好⽤,⽐如 线程 中定义线程的执⾏函数逻辑,智能指针 中定制 删除器 等,lambda
的应⽤还是很⼴泛的,以后我们会不断接触到
三、捕捉列表
3.1概念与功能描述
lambda
表达式中默认只能用 lambda
函数体和参数中的变量,如果想运⽤外层作⽤域中的变量则需要进行 “捕捉”
- 第一种捕捉方式:在捕获列表中显示 传值捕获(变量只读状态),传引用捕获,捕获多个变量用逗号进行分割。
[ x, y, &z ]
表示x
和y
值捕获,z
引用捕获 - 第⼆种捕捉方式:在捕捉列表中隐式捕捉,在捕捉列表写⼀个
=
表⽰隐式值捕捉(将变量全部变为值捕捉),在捕捉列表 写⼀个&
表⽰隐式引用捕捉,这样我们lambda
表达式中使用的那些变量,编译器就会对其进行⾃动捕捉
注意:隐式捕获,不是将程序中的所有变量都捕捉到 **lambda** 表达式中,而是需要哪个,捕获哪个
- 第三种捕捉方式:在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉,
[ = , &x ]
表⽰其他变量隐式值捕捉,x
引⽤捕捉;[ &, x, y ]
表⽰其他变量引⽤捕捉,x
和y
值捕捉。当使⽤混合捕捉时,第⼀个元素必须是& 或 =
,并且&
混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=
混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。
lambda
表达式在函数局部域中,他可以捕捉lambda
位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda
表达式中可以直接使用。这也意味着**lambda
** 表达式如果定义在全局位置,捕获列表必须为空
3.2mutable
- 默认情况下,
lambda
捕捉列表是被const
修饰的,也就是说传值捕捉而来的对象不能被修改,mutable
加在参数列表的后⾯可以 取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以被修改了,但是修改还是形参对象,不会影响实参(类似值传递,返回的是自身数据的一份临时拷贝)— — 被 lambda 通过 “传值捕获” 的内部变量,本质是外部变量的一份临时拷贝。使用mutable
修饰符后,参数列表不可省略(即使参数不能为空)。
四、Lambda 的原理
lambda
的原理和 范围for 很像,编译后从汇编指令层的⻆度看,压根就没有 lambda
和 范围for 这样的东西。范围for 底层是迭代器,⽽lambda
底层是仿函数对象,也就说我们写了⼀个 lambda
以后,编译器会⽣成⼀个对应的仿函数的类
仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda
⽣成的类名不同,lambda
参数/返 回类型/函数体就是仿函数 operator()
的参数/返回类型/函数体, lambda
的捕获列表本质是⽣成 的仿函数类的成员变量,也就是说捕获列表的变量都是 lambda
类构造函数的实参,当然隐式捕获,编译器要看使⽤哪些就传那些对象
- 上⾯的原理,我们可以透过 汇编层 了解⼀下
class Rate
{
public:
Rate(double rate)
: rate(rate) {}
double operator()(double money, int year)
{
return money * rate * year;
}
private:
double rate;
};
int main()
{
double rate = 0.49;
//仿函数对象
Rate r1(rate);
r1(1000, 2);
auto func1 = []()
{
cout << "Hello World" << endl;
}; func1();
//lambda
//捕获列表中的rate,可以视作 lambda 类构造函数的参数传递
auto r2 = [rate](double money, int year)
{
return money * rate * year;
};
r2(1000, 2);
return 0;
}
- 本质上都是给构造函数传参
-
定义
lambda
- 生成仿函数 -
定义
lambda
对象 - 初始化仿函数对象
五、Lambda 捕获悬垂引用问题
此外,多线程中如果 捕获引用,也可能出现 引用失效 的问题,这会导致程序结果错误或访问异常等;而对于 传值捕获 则不会出现这种问题
🌟 各位看官好,我是工藤新一¹呀~
🌈 愿各位心中所想,终有所致!