C++11---lambda表达式

news2024/11/23 4:00:42

lambda表达式

  • lambda表达式概念
    • lambda表达式语法
      • lambda表达式各部分说明
  • lambda表达式交换两个数
  • lambda表达式底层原理
      • lambda表达式的底层原理
  • lambda表达式之间不能相互赋值

lambda表达式概念

lambda表达式是一个匿名函数,恰当使用lambda表达式可以让代码变得简洁,并且可以提高代码的可读性。
举个例子
商品类Goods的定义如下:

struct Goods
{
	string _name;  //名字
	double _price; //价格
	int _num;      //数量
};

现在要对若干商品分别按照价格和数量进行升序、降序排序。

要对一个数据集合中的元素进行排序,可以使用sort函数,但由于这里待排序的元素为自定义类型,因此需要用户自行定义排序时的比较规则。
要控制sort函数的比较方式常见的有两种方法,一种是对商品类的的()运算符进行重载,另一种是通过仿函数来指定比较的方式。
显然通过重载商品类的()运算符是不可行的,因为这里要求分别按照价格和数量进行升序、降序排序,每次排序就去修改一下比较方式是很笨的做法。
所以这里选择传入仿函数来指定排序时的比较方式。比如:

struct ComparePriceLess
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price > g2._price;
	}
};
struct CompareNumLess
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._num < g2._num;
	}
};
struct CompareNumGreater
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._num > g2._num;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 300 }, { "香蕉", 3.3, 100 }, { "橙子", 2.2, 1000 }, { "菠萝", 1.5, 1 } };
	sort(v.begin(), v.end(), ComparePriceLess());    //按价格升序排序
	sort(v.begin(), v.end(), ComparePriceGreater()); //按价格降序排序
	sort(v.begin(), v.end(), CompareNumLess());      //按数量升序排序
	sort(v.begin(), v.end(), CompareNumGreater());   //按数量降序排序
	return 0;
}

仿函数确实能够解决这里的问题,但可能仿函数的定义位置可能和使用仿函数的地方隔得比较远,这就要求仿函数的命名必须要通俗易懂,否则会降低代码的可读性。

对于这种场景就比较适合使用lambda表达式。比如:

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 300 }, { "香蕉", 3.3, 100 }, { "橙子", 2.2, 1000 }, { "菠萝", 1.5, 1 } };
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price; 
	}); //按价格升序排序
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
	{
		return g1._price > g2._price;
	}); //按价格降序排序
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
	{
		return g1._num < g2._num;
	}); //按数量升序排序
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
	{
		return g1._num > g2._num;
	}); //按数量降序排序
	return 0;
}

这样一来,每次调用sort函数时只需要传入一个lambda表达式指明比较方式即可,阅读代码的人一看到lambda表达式就知道本次排序的比较方式是怎样的,提高了代码的可读性。

lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters)mutable->return-type{statement}

lambda表达式各部分说明

[capture-list]:捕捉列表。该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可以省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
lambda函数的参数列表和返回值类型都是可选部分,但捕捉列表和函数体是不可省略的,因此最简单的lambda函数如下:

int main()
{
	[]{}; //最简单的lambda表达式
	return 0;
}

捕获列表说明
捕获列表描述了上下文中哪些数据可以被lambda函数使用,以及使用的方式是传值还是传引用。

[var]:表示值传递方式捕捉变量var。
[=]:表示值传递方式捕获所有父作用域中的变量(成员函数包括this指针)。
[&var]:表示引用传递捕捉变量var。
[&]:表示引用传递捕捉所有父作用域中的变量(成员函数包括this指针)。
[this]:表示值传递方式捕捉当前的this指针。
说明一下:

父作用域指的是包含lambda函数的语句块。
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如[=, &a, &b]。
捕捉列表不允许变量重复传递,否则会导致编译错误。比如[=, a]重复传递了变量a。
在块作用域以外的lambda函数捕捉列表必须为空,即全局lambda函数的捕捉列表必须为空。
在块作用域中的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
lambda表达式之间不能相互赋值,即使看起来类型相同。

lambda表达式交换两个数

如果要用lambda表达式交换两个数,可以有以下几种写法:
标准写法
参数列表中包含两个形参,表示需要交换的两个数,注意需要以引用的方式传递。比如:

int main()
{
	int a = 10, b = 20;
	auto Swap = [](int& x, int& y)->void
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	Swap(a, b); //交换a和b
	return 0;
}

说明一下:
lambda表达式是一个匿名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量,此时这个变量就可以像普通函数一样使用。
lambda表达式的函数体在格式上并不是必须写成一行,如果函数体太长可以进行换行,但换行后不要忘了函数体最后还有一个分号。
利用捕捉列表进行捕捉
以引用的方式捕捉所有父作用域中的变量,省略参数列表和返回值类型。比如:

int main()
{
	int a = 10, b = 20;
	auto Swap = [&]
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	Swap(); //交换a和b
	return 0;
}

这样一来,调用lambda表达式时就不用传入参数了,但实际我们只需要用到变量a和变量b,没有必要把父作用域中的所有变量都进行捕捉,因此也可以只对父作用域中的a、b变量进行捕捉。比如:

int main()
{
	int a = 10, b = 20;
	auto Swap = [&a, &b]
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	Swap(); //交换a和b
	return 0;
}

说明一下: 实际当我们以[&]或[=]的方式捕获变量时,编译器也不一定会把父作用域中所有的变量捕获进来,编译器可能只会对lambda表达式中用到的变量进行捕获,没有必要把用不到的变量也捕获进来,这个主要看编译器的具体实现。
传值方式捕捉?
如果以传值方式进行捕捉,那么首先编译不会通过,因为传值捕获到的变量默认是不可修改的,如果要取消其常量性,就需要在lambda表达式中加上mutable,并且此时参数列表不可省略。比如:

int main()
{
	int a = 10, b = 20;
	auto Swap = [a, b]()mutable
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	Swap(); //交换a和b?
	return 0;
}

但由于这里是传值捕捉,lambda函数中对a和b的修改不会影响外面的a、b变量,与函数的传值传参是一个道理,因此这种方法无法完成两个数的交换。

lambda表达式底层原理

lambda表达式的底层原理

实际编译器在底层对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的。函数对象就是我们平常所说的仿函数,就是在类中对()运算符进行了重载的类对象。

下面编写了一个Add类,该类对()运算符进行了重载,因此Add类实例化出的add1对象就叫做函数对象,add1可以像函数一样使用。然后我们编写了一个lambda表达式,并借助auto将其赋值给add2对象,这时add1和add2都可以像普通函数一样使用。比如:

class Add
{
public:
	Add(int base)
		:_base(base)
	{}
	int operator()(int num)
	{
		return _base + num;
	}
private:
	int _base;
};
int main()
{
	int base = 1;

	//函数对象
	Add add1(base);
	add1(1000);

	//lambda表达式
	auto add2 = [base](int num)->int
	{
		return base + num;
	};
	add2(1000);
	return 0;
}

调试代码并转到反汇编,可以看到:

在创建函数对象add1时,会调用Add类的构造函数。
在使用函数对象add1时,会调用Add类的()运算符重载函数。
如下图:
在这里插入图片描述
本质就是因为lambda表达式在底层被转换成了仿函数。

当我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对()运算符进行重载,实际lambda函数体的实现就是这个仿函数的operator()的实现。
在调用lambda表达式时,参数列表和捕获列表的参数,最终都传递给了仿函数的operator()。
lambda表达式和范围for是类似的,它们在语法层面上看起来都很神奇,但实际范围for底层就是通过迭代器实现的,lambda表达式底层的处理方式和函数对象是一样的。

lambda表达式之间不能相互赋值

lambda表达式之间不能相互赋值,就算是两个一模一样的lambda表达式。

因为lambda表达式底层的处理方式和仿函数是一样的,在VS下,lambda表达式在底层会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>。
类名中的uuid叫做通用唯一识别码(Universally Unique Identifier),简单来说,uuid就是通过算法生成一串字符串,保证在当前程序当中每次生成的uuid都不会重复。
lambda表达式底层的类名包含uuid,这样就能保证每个lambda表达式底层类名都是唯一的。
因此每个lambda表达式的类型都是不同的,这也就是lambda表达式之间不能相互赋值的原因,我们可以通过typeid(变量名).name()的方式来获取lambda表达式的类型。比如:

int main()
{
	int a = 10, b = 20;
	auto Swap1 = [](int& x, int& y)->void
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	auto Swap2 = [](int& x, int& y)->void
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	cout << typeid(Swap1).name() << endl; //class <lambda_797a0f7342ee38a60521450c0863d41f>
	cout << typeid(Swap2).name() << endl; //class <lambda_f7574cd5b805c37a13a7dc214d824b1f>
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1453056.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux CPU 性能分析工具火焰图(Flame Graphs)认知

写在前面 博文内容为 《BPF Performance Tools》 读书笔记整理详细了解小伙伴可以访问作者官网&#xff1a;https://www.brendangregg.com/flamegraphs.html有油管上分享的作者在USENIX ATC 2017 的视屏理解不足小伙伴帮忙指正 不必太纠结于当下&#xff0c;也不必太忧虑未来&a…

Day-01-01

项目准备 项目介绍 1. 业务功能 本项目包括了用户端、机构端、运营端。 2. 技术选型 各层简要说明&#xff1a; 环境配置 1. 版本信息 在开发过程中&#xff0c;个人使用gitee作为版本控制工具。 2. 虚拟机安装说明 根据所提供资料&#xff0c;虚拟机IP地址已设置为192.1…

OpenAI Sora出炉,视频鉴赏,详细介绍,小白看过来~~立即尝试Sora,开启您的AI视频创作之旅吧!

OpenAI最新推出的视频创作的颠覆性产品&#xff1a;Sora&#xff0c;它开启了该行业的新纪元&#xff0c;吊打目前一众视频制作工具。 无论是专业人士还是爱好者&#xff0c;都可以轻松创作出高质量的视频内容。 Sora同样是一个根据文本指令创建逼真而富有想象力的场景的人工智…

图表示学习 Graph Representation Learning chapter2 背景知识和传统方法

图表示学习 Graph Representation Learning chapter2 背景知识和传统方法 2.1 图统计和核方法2.1.1 节点层次的统计和特征节点的度 节点中心度聚类系数Closed Triangles, Ego Graphs, and Motifs 图层次的特征和图的核节点袋Weisfieler–Lehman核Graphlets和基于路径的方法 邻域…

Flex布局简介及微信小程序视图层View详解

目录 一、Flex布局简介 什么是flex布局&#xff1f; flex属性 基本语法和常用属性 Flex 布局技巧 二、视图层View View简介 微信小程序View视图层 WXML 数据绑定 列表渲染 条件渲染 模板 WXSS 样式导入 内联样式 选择器 全局样式与局部样式 WXS 示例 注意事项…

阅读笔记(SOFT COMPUTING 2018)Seam elimination based on Curvelet for image stitching

参考文献&#xff1a; Wang Z, Yang Z. Seam elimination based on Curvelet for image stitching[J]. Soft Computing, 2018: 1-16. 注&#xff1a;SOFT COMPUTING 大类学科小类学科Top期刊综述期刊工程技术 3区 COMPUTER SCIENCE, ARTIFICIAL INTELLIGENCE 计算机&#xf…

二次元风格404页面源码

大气二次元风格带背景音乐404页面模板 蓝奏云&#xff1a;https://wfr.lanzout.com/ivaYi1odtjhe

中科院一区论文复现,改进蜣螂算法,Fuch映射+反向学习+自适应步长+随机差分变异,MATLAB代码...

本期文章复现一篇发表于2024年来自中科院一区TOP顶刊《Energy》的改进蜣螂算法。 论文引用如下&#xff1a; Li Y, Sun K, Yao Q, et al. A dual-optimization wind speed forecasting model based on deep learning and improved dung beetle optimization algorithm[J]. Ener…

Docker容器运行

1、通过--name参数显示地为容器命名&#xff0c;例如:docker run --name “my_http_server” -d httpd 2、容器重命名可以使用docker rename。 3、两种进入容器的方法&#xff1a; 3.1、Docker attach 例如&#xff1a; 每间隔一秒打印”Hello World”。 Sudo docker run…

高程 | 类与对象(c++)

文章目录 &#x1f4da;面向对象程序设计的基本特点&#x1f407;抽象——概括问题&#xff0c;抽出公共性质并加以描述。&#x1f407;封装——将抽象所得数据和行为相结合&#xff0c;形成一个有机的整体&#xff0c;形成“类”。&#x1f407;继承——在原有类特性的基础上&…

小白必看,总结前端所有主流的构建工具,webpack / vite / roollup / esbuild,包含源码,建议关注+收藏

前言 本篇文章旨在总结前端常见的构建工具&#xff0c;构建工具是前端工程化中的重要的组成部分。 在实际项目中&#xff0c;我们初始化项目&#xff0c;一般是使用脚手架命令一键生成的&#xff0c;比如说使用 create-vue 初始化 vue 项目的时候&#xff0c;就会默认使用 vi…

高效宣讲管理:Java+SpringBoot实战

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

Stable Diffusion 模型下载:Beautiful Realistic Asians(美丽真实的亚洲人)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 Beautiful Realistic Asians&#xff08;BRA&#xff09;模型是由作者自己训练…

流程结构

章节目录&#xff1a; 一、选择结构1.1 if 语句1.2 if...else 语句1.3 嵌套 if1.4 if...else if...else1.5 三目运算符1.6 switch 语句 二、循环结构2.1 while 语句2.2 do...while 语句2.3 for 语句2.4 嵌套循环 三、跳转语句3.1 break 语句3.2 continue 语句3.3 goto 语句 四、…

OpenAI 发布 Sora,可快速生成视频

作者&#xff1a;苍何&#xff0c;前大厂高级 Java 工程师&#xff0c;阿里云专家博主&#xff0c;CSDN 2023 年 实力新星&#xff0c;土木转码&#xff0c;现任部门技术 leader&#xff0c;专注于互联网技术分享&#xff0c;职场经验分享。 &#x1f525;热门文章推荐&#xf…

点亮代码之灯,程序员的夜与电脑

在科技的海洋里&#xff0c;程序员是那些驾驶着代码船只&#xff0c;穿梭于虚拟世界的探险家。他们手中的键盘是航行的舵&#xff0c;而那台始终不愿关闭的电脑&#xff0c;便是他们眼中永不熄灭的灯塔。有人说&#xff0c;程序员不喜欢关电脑&#xff0c;这究竟是为什么呢&…

Rust 学习笔记 - 注释全解

前言 和其他编程语言一样&#xff0c;Rust 也提供了代码注释的功能&#xff0c;注释用于解释代码的作用和目的&#xff0c;帮助开发者理解代码的行为&#xff0c;编译器在编译时会忽略它们。 单行注释 单行注释以两个斜杠 (//) 开始&#xff0c;只影响它们后面直到行末的内容…

阿里云服务器租用价格 2024年新版活动报价及租用收费标准

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

模拟电子技术——同相比例运算放大电路、反向运算比例放大电路、反向加法器电路、差分减法器电路

文章目录 一、同相比例运算放大电路什么是比例运算放大电路线性区与非线性区电压跟随器 二、反向运算比例放大电路什么是反比例运算放大器电路及特点 三、反向加法器电路什么是反向加法器电路及特点及参数计算电路及特点及参数计算 四、差分减法器电路什么是差动减法器 总结 提…

备战蓝桥杯---图论之建图基础

话不多说&#xff0c;直接看题&#xff1a; 首先&#xff0c;这个不是按照字典序的顺序&#xff0c;而是以只要1先做&#xff0c;在满足后让2先做。。。。 就是让数字小的放前面做拓扑排序。 我们可以先做1&#xff0c;看看它的前驱。 举个例子&#xff1a; 我们肯定要把1放…