C++ —— Lambda 表达式

发布于:2025-05-20 ⋅ 阅读:(11) ⋅ 点赞:(0)

在这里插入图片描述
在这里插入图片描述

🎁个人主页:工藤新一¹

🔍系列专栏: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 ] 表示 xy 值捕获,z 引用捕获
  • 第⼆种捕捉方式:在捕捉列表中隐式捕捉,在捕捉列表写⼀个 = 表⽰隐式值捕捉(将变量全部变为值捕捉),在捕捉列表 写⼀个 & 表⽰隐式引用捕捉,这样我们 lambda 表达式中使用的那些变量,编译器就会对其进行⾃动捕捉

注意:隐式捕获,不是将程序中的所有变量都捕捉到 **lambda** 表达式中,而是需要哪个,捕获哪个

  • 第三种捕捉方式:在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉, [ = , &x ] 表⽰其他变量隐式值捕捉, x 引⽤捕捉;[ &, x, y ] 表⽰其他变量引⽤捕捉,xy 值捕捉。当使⽤混合捕捉时,第⼀个元素必须是 & 或 =,并且 & 混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理 = 混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


  • 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 捕获悬垂引用问题

在这里插入图片描述

此外,多线程中如果 捕获引用,也可能出现 引用失效 的问题,这会导致程序结果错误或访问异常等;而对于 传值捕获 则不会出现这种问题


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

🌟 各位看官好我是工藤新一¹呀~

🌈 愿各位心中所想,终有所致!


网站公告

今日签到

点亮在社区的每一天
去签到