C++11 lambda表达式

前言

上几期我们介绍了类的新功能,右值引用、完美转发语法特性,本期继续介绍C++11的新语法特性,即lambda表达式!

目录

前言

lambda表达式

lambda的引入

什么是lambda 表达式

lambda表达式的语法

捕捉列表说明

lambda的底层原理

什么是uuid?


lambda表达式

lambda 表达式 源于数学中的 λ 演算,λ 演算是一种 基于函数的形式化系统,它由数学家 阿隆佐邱奇 提出,用于研究抽象计算和函数定义。对于编程领域来说,可以使用 lambda 表达式 快速构建函数对象,作为函数中的参数。下面我们就来介绍一下C++中的 lambda

lambda的引入

在C++98中,如果要想对一个数据的集合进行排序,如果是内置类型则可以直接使用std::sort函数:

int nums[] = { 5,2,6,1,9,34,21,11 };
cout << "排序前:";
for (const auto& e : nums) cout << e << " ";
cout << endl;

sort(nums, nums + sizeof(nums) / sizeof(nums[0]));// 默认是升序
cout << "升序排序:";
for (const auto& e : nums) cout << e << " ";
cout << endl;

sort(nums, nums + sizeof(nums) / sizeof(nums[0]), greater<int>());// 使用greater仿函数,控制成降序
cout << "降序排序:";
for (const auto& e : nums) cout << e << " ";
cout << endl;

注意:这里使用sort时,需要引入 <algorithm> 这个算法头文件!

这里的greater<T>()不在介绍了,我们在前面优先级队列以及介绍sort的时候就介绍了!

OK,上述的内置类型排序没有问题;但是现实中只对内置类型排序的场景很少,一般都是对自定义类型的某个属性排序,而自定义排序时用户需要自己指定比较规则

struct Goods
{
	string _name;	// 名字
	double _price;  // 价格
	int _evaluate;  // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};


int main()
{
	vector<Goods> goods = { {"苹果",9.9, 7}, {"香蕉", 10.3, 8}, {"西瓜", 1.2,9} };
	return 0;
}

例如现在需要对,goods的元素以 价格最高(降序)和价格最低(升序)评价最高(降序)和评价最低(升序)进行对商品排序;此时就需要用户自己写出四个仿函数,来控制:

1、价格升序

struct CmpPriceLess
{
	bool operator()(const Goods& a, const Goods& b)
	{
		return a._price < b._price;
	}
};

2、价格降序

struct CmpPriceGreater
{
	bool operator()(const Goods& a, const Goods& b)
	{
		return a._price > b._price;
	}
};

3、评价升序

struct CmpEvaluateLess
{
	bool operator()(const Goods& a, const Goods& b)
	{
		return a._evaluate < b._evaluate;
	}
};

 

4、评价降序

struct CmpEvaluateGreater
{
	bool operator()(const Goods& a, const Goods& b)
	{
		return a._evaluate > b._evaluate;
	}
};

OK,这样实现了对需求的排序!但是这样写,属实有点麻烦!每次都要实现一个类,如果比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这给程序员带来了极大的不便。因此,C++11引入了lambda表达式!

什么是lambda 表达式

OK,我就直接先说结论了!

lambda表达式的本质就是一个匿名函数对象!

这里你可能不理解,我都没见过lambda表达式是啥样,你一上来就给我纯理论!确实这里,有些残暴了!不过没关系,我们下面先来见一见,然后在理解这句话:

vector<Goods> goods = { {"苹果",9.9, 7}, {"香蕉", 10.3, 8}, {"西瓜", 1.2,9} };
sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b) 
	{
		return a._price < a._price;// 价格升序
	});

sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b)
	{
		return a._price > a._price;// 价格降序
	});

sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b)
	{
		return a._evaluate < a._evaluate;// 评价升序
	});

sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b)
	{
		return  a._evaluate > a._evaluate;// 评价降序
	});

上面的四个仿函数对直接没了,直接变成了后面的这些!为什么说lambda的本质就是一个匿名的函数对象呢?以前,这个位置不就是一个仿函数的对象吗!现在只不过是,把这个对象换成了直接直接实现罢了,且没有名字!

lambda表达式的语法

OK,上面见了一下,但是你可能有些懵,没关系我们下面就来介绍lambda表达式的语法!

lambda表达式的书写格式

[capture-list] (parameters) mutable -> return-type {statement};

lambda表达式的各部分说明:

[capture-list] :捕捉列表,该列表总是出现lambda表达式的开始位置不可省略,但是可以为空),编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量,供lambda函数使用!

(parameters) :参数列表。与普通函数的参数列表一样,如果不需要传递参数,则可以连同()一块省略!

mutable : 取消参数的常性。一般情况下,lambda表达式总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即参数为空)!

-> return-type :返回值类型。用于追踪返回类型形式声明函数的返回值,没有返回值这部分则可以省略。返回值类型明确的情况下,也可以省略,由编译器自动推导

{statement} :函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕捉到的变量!

注意在lambda函数定义中,参数列表和返回值类型都是可选的部分(可省略),而不追列表和函数体是必须的,但是可以为空!因此在C++11中,最简单的lambda函数为:[]{}; 该lambda函数不做任何的事情!


OK,有了对语法的介绍,我们就可以理解上面写的排序了:

sort(goods.begin(), goods.end(), [](const Goods& a, const Goods& b)
	{
		return  a._evaluate > a._evaluate;// 评价降序
	});

这里,因为只是比较,不对参数进行修改,所以没加 mutable , 而函数体内的返回值显然是 bool 类型,所以直接省略掉返回值类型!当然你也可以加上:

当然吗,我们也可以自己写一写,lambda函数:

[] {};// 最简单的 lambda函数,无实际的意义

[](const int& a, const int& b) ->int {return a + b; };

OK,第一个我么是介绍过的;这里主要看第二个:这是一个简单的add函数,实现的是两个整数的加法(返回值可省略)!现在的问题是如何调用这个匿名函数,匿名函数对象的生命周期是在当前行,所以,第一种用法就是直接调用:

第二种就是,使用auto 推导出匿名函数的类型,然后赋值给一个变量:

第二种,这里就是我们以前介绍的移动构造/拷贝构造;因为过了当前行匿名函数就要销毁了,他就是一个将亡值,此时如果这个类实现了移动构造就会把这个对象的资源转移给add,如果没有实现移动构造就会调用拷贝构造!这里这样说你可能不太理解,这得介绍了lambda的原理才可以理解!后面会介绍 ,也会在提这个点的!

捕捉列表说明

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

• [var]:表示值传递的方式捕捉变量var(默认是const修饰的)

• [=]:表示值传递的方式捕捉所有父作用域中的变量,包含this

• [&var]:表示引用传递捕捉变量var

• [&]:表示引用传递的方式捕捉所有父作用域中的变量,包含this

• [this]:表示值传递的方式捕捉当前的this指针

注意:

1、父作用域指的是包含lambda函数的语块

2、语法上捕捉列表可由多个捕捉项组成,并以逗号分割

例如:[&, a,this]表示以引用的方式捕捉除了a,this以外的所有变量,a/this用传值捕捉!在如:[=,&a,&b]表示:以传值的方式捕捉除了a和b以外的所有变量,用引用的方式捕捉a和b;

3、捕捉列表不允许变量重复传递,否则就会导致编译错误。

例如:[=,a],此时,a已经被捕捉,后面有传递就会导致重复捕捉!

4、在块作用域以外的lambda函数捕捉列表必须为空!

这句话的意思就是说,如果一个全局的lambda的函数,它的捕捉列表必须为空!因为他没有父作用域!

5、在块作用域以外的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非此作用域中的局部变量或者全局变量都会导致编译报错

6、lambda表达式直接不能相互赋值,即使看起来类型相同也不行!

第6点这个也是要结合底层原理理解的!后面再提,这里先看看:

看到这个报错,我们大概猜测lambda的底层会不会就是仿函数呢?其实是的!

OK,我们到此lambda的语法基本介绍完了,我们来写几个栗子,用一下:

1、演示mutable的作用

我们知道[var]默认捕捉的值是被const修饰的,如果我们想让他修改,该如何做呢?就用mutable,告诉编译器该捕捉可以修改:我们以经典的两数交换来演示

此时,是不允许a和b修改的,要想修改加上mutable即可:

此时,由于是传值捕捉,所以只是改变了形参,外面的实参依旧是没变:

2、演示[=]捕捉

int a = 3, b = 5;
auto f = [=] {return a + b; };
cout << f() << endl;

这里没有对捕捉的值修改,所以不加mutable,如果修改就继续得加上!

3、验证[&var]捕捉

int a = 3, b = 5;
auto swap1 = [&a, &b]() mutable
{
	int tmp = a;
	a = b;
	b = tmp;
};

此时这里加不加mutable都是一样的!因为此时就是外面的别名!

4、验证[&]捕捉

int a = 3, b = 5;
auto swap1 = [&]()
{
	int tmp = a;
	a = b;
	b = tmp;
};

虽然最后两种写法也可以实现效果,但是标准的写法,swap的标准写法是

auto swap = [](int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
};

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(10000, 2);// 函数对象

	 //lambda
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
	r2(100, 1);
	return 0;
}

我们可以跳到反汇编看看,底层实现: 

我们可以看到,lambda的底层就是去创建了一个类,这个类是lambda_uuid,然后又去调用了(), 也就是说,lambda表达式是编译器帮你创建了一个lambda_uuid的类,然后重载实现了(),最后去调用了()

什么是uuid?

UUID是Universally Unique Identifier(通用唯一识别码)的缩写,它是一个128位(16字节)长的数字,用于确保在时间和空间上的唯一性。

它的优点是,生成的标识符几乎就是唯一的!所以我们每一个lambda表达式对应一个uuid的类,uuid的重复率极低,所以即使你的两个lambda一样,但是他们的uuid不一样!

我们可以写一个lambda看看:

auto f1 = [] {cout << "hello world"; };
auto f2 = [] {cout << "hello world"; };
cout << typeid(f1).name() << endl;
cout << typeid(f2).name() << endl;

这也解释了我们前面的,为什么两个两相同的lambda不能赋值!原因是,他们底层的类一样!也就是不也是同一类型的对象!

为什么说是将匿名的lambda函数对象赋值给一个变量是,走的移动构造/拷贝构造?原因是,编译器生成的这个类或生成一个默认的移动构造,而匿名函数的销毁时把资源转移给了那个引用的对象!


OK,好兄弟,本期分享就到这里,我是cp我们下期再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

卡西欧相机SD卡格式化后数据恢复指南

在数字摄影时代&#xff0c;卡西欧相机以其卓越的性能和便携性成为了众多摄影爱好者的首选。然而&#xff0c;随着拍摄量的增加&#xff0c;SD卡中的数据管理变得尤为重要。不幸的是&#xff0c;有时我们可能会因为操作失误或系统故障而将SD卡格式化&#xff0c;导致珍贵的照片…

Linux笔记---简单指令

1. 使用的环境 博主使用的是华为云服务器xshell终端的方式学习的&#xff0c;因为据说这样的方式比较接近以后的工作环境。 其中云服务器安装的是Ubuntu操作系统(以Linux为内核&#xff0c;适合新手学习Linux的一个版本) 这里的云服务器不一定使用华为的&#xff0c;但是我在…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)

十三、文章分类页面 - [element-plus 表格] Git仓库&#xff1a;https://gitee.com/msyycn/vue3-hei-ma.git 基本架子 - PageContainer 功能需求说明&#xff1a; 基本架子-PageContainer封装文章分类渲染 & loading处理文章分类添加编辑[element-plus弹层]文章分类删除…

win7自带壁纸丢失主题丢失

有时候盗版破解或者其他美化工具会导致win7自带的壁纸丢失&#xff0c;从个性化管理里面无法恢复原始的壁纸&#xff08;如下图&#xff09;&#xff0c;但是由于工作原因公司的电脑又不方便设置第三方的壁纸&#xff0c;所以找了一下解决方案。 经典问题&#xff0c;百度找到的…

用户态缓存:高效数据交互与性能优化

目录 1. 用户态缓存区工作背景 1.1 为什么每条连接都需要读写缓存区 1.1.1 读缓存区&#xff08;Read Buffer&#xff09; 1.1.2 写缓存区&#xff08;Write Buffer&#xff09; 1.2 用户态缓存区的工作流程 1.3 用户态缓存区的重要性 2. UDP 和 TCP 的设计差异 2.1 UD…

机器翻译与数据集_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录介绍机器翻译下载和预处理数据集词元化词表加载数据集训练模型对上述代码中出现的Vocab进行总体解释和逐行解释使用场景 小结练习答案1. num_examples 参数对词表大小的影响2. 对于没有单词边界的语言&#xff0c;单词级词元化的有效性 介绍…

关于 Visual Studio Code 如何插入自定义快捷方式

第一步&#xff1a;打开控制面板&#xff0c;也可以使用快捷键ctrlshiftp 然后点击命令面板 第二步&#xff1a;输入snippets搜索&#xff0c;选择配置用户代码片段 第三步&#xff1a;选择新建全局代码片段文件&#xff0c;然后输入文件名&#xff0c;这里我因为创建的是vue的…

解决uniapp开发的app,手机预览,上下滑动页面,页面出现拉伸,抖动的效果问题,

在pages.json文件里“globalStyle”下面的"app-plus"里加入"bounce": "none"即可 "app-plus": { "bounce": "none", //关闭窗口回弹效果 }

2024年华为杯数学建模研赛(C题) 建模解析| 磁芯损耗建模 | 小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮2000人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;实现综合建模。独创复杂系统视角&#xff0c;帮助你解决研赛的难关呀。 完整内容可…

2024华为杯研究生数学建模竞赛(研赛)选题建议+初步分析

提示&#xff1a;C君认为的难度&#xff1a;DE<C<F&#xff0c;开放度&#xff1a;CDE>F。 华为专项的题目&#xff08;A、B题&#xff09;暂不进行选题分析&#xff0c;不太建议大多数同学选择&#xff0c;对自己专业技能有很大自信的可以选择华为专项的题目。后续会…

Mysql_使用简介

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

2024华为杯研赛D题保姆级教程思路分析+教程

2024年中国研究生数学建模竞赛D题保姆级教程思路分析 D题&#xff1a;大数据驱动的地理综合问题&#xff08;数学分析&#xff0c;统计学&#xff09; 关键词&#xff1a;地理、气候、统计&#xff08;细致到此题&#xff1a;统计指标、统计模型、统计结果解释&#xff09; …

Linux通过yum安装Docker

目录 一、安装环境 1.1. 旧的docker包卸载 1.2. 安装常规环境包 1.3. 设置存储库 二、安装Docker社区版 三、解决拉取镜像失败 3.1. 创建文件目录/etc/docker 3.2. 写入镜像配置 https://docs.docker.com/engine/install/centos/ 检测操作系统版本&#xff0c;我操作的…

OceanBase 中 schema 的定义与应用

背景 经常在OceanBase 的问答社区 里看到一些关于 “schema 是什么” 的提问。 先纠正一些同学的误解&#xff0c; OceanBase 中的 Schema 并不简单的等同于 Database&#xff0c;本次分享将探讨 OceanBase 中的Schema是什么&#xff0c;及一些大家经常遇到的问题。 具体而…

PDF——压缩大小的方法

方法一&#xff1a;QQ浏览器->格式转换->PDF转纯图PDF

萌啦数据行业数据在哪看,萌啦ozon行业数据怎么看

在跨境电商的浪潮中&#xff0c;数据已成为商家决策的重要基石。萌啦Ozon数据行业分析板块&#xff0c;作为连接商家与市场动态的桥梁&#xff0c;为商家提供了丰富的行业洞察与精准的市场指导。本文将带您深入探索萌啦Ozon数据行业分析板块的功能&#xff0c;揭秘如何在这片数…

IDEA中实现springboot热部署

IDEA中实现springboot热部署 热部署: 每一次修改代码后会自动更新&#xff0c;无需每次重启 依赖(pom.xml) 修改后记得Reload一下 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><…

重生归来之挖掘stm32底层知识(1)——寄存器

概念理解 要使用stm32首先要知道什么是引脚和寄存器。 如下图所示&#xff0c;芯片通过这些金属丝与电路板连接&#xff0c;这些金属丝叫做引脚。一般做软件开发是不需要了解芯片是怎么焊的&#xff0c;只要会使用就行。我们平常通过编程来控制这些引脚的输入和输出&#xff0c…

农业电商服务系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;会员管理&#xff0c;商家管理&#xff0c;商品分类管理&#xff0c;商品信息管理&#xff0c;农产品监督管理&#xff0c;助农信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页…

7000长文:一文读懂Agent,大模型的下一站

什么是Agent&#xff1f;为什么是Agent&#xff1f; 大模型除了Chat外还能做什么用&#xff1f; 当我们将大型模型视为“核心调度器“时&#xff0c;它就变成了我们的Agent。借助任务规划、记忆及外部工具等能力&#xff0c;大型模型能够识别出应该执行的任务以及执行方式&…