【C++中的lambda表达式】

不需要借口,爱淡了就放手.......................................................................................................

文章目录

前言

一、【lambda表达式介绍】

1、【lamda表达式的概念】

2、【lamda表达式的语法】

二、【lambda表达式的使用】

1、【标准写法】

2、【利用捕捉列表进行捕捉】

3、【传值方式捕捉?】

4、【lambda表达式之间不能相互赋值】

三、【lambda表达式底层原理】

总结


前言

本篇讲述了C++中一个重要的语法lamda表达式,该语法在python,java当中也存在它不仅可以提高代码可读性还可以简化代码的编写,还请耐心观看本篇博客。


一、【lambda表达式介绍】

1、【lamda表达式的概念】

实际上说,lambda表达式是一个匿名函数,也就是当使用lambda表达式可以让代码变得简洁,并且可以提高代码的可读性。

首先我们看一个例子:

商品类Goods的定义如下:

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

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

我们知道要对一个数据集合中的元素进行排序,可以使用sort函数,但由于这里待排序的元素为自定义类型,因此需要我们自行定义排序时的比较规则。

  • 要控制sort函数的比较方式常见的有两种方法,一种是对商品类的的()运算符进行重载,另一种是通过仿函数来指定比较的方式。
  • 显然通过重载商品类的()运算符是不可行的,因为这里要求分别按照价格和数量进行升序、降序排序,每次排序就去修改一下比较方式是很笨的做法。

所以这里选择传入仿函数来指定排序时的比较方式。比如:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
struct Goods
{string _name;  //名字double _price; //价格int _num;      //数量
};
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;
}

我们重点看[](const Goods& g1,const Goods&g2){return ...},这部分,这部分就叫作lamda表达式,我们可以看到,它能够传入sort函数,作为比较函数,这样一来,每次调用sort函数时只需要传入一个lambda表达式指明比较方式即可,阅读代码的人一看到lambda表达式就知道本次排序的比较方式是怎样的,提高了代码的可读性。

2、【lamda表达式的语法】

lambda表达式书写格式:

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

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

lambda函数的参数列表和返回值类型都是可选部分,但捕捉列表和函数体是不可省略的,因此最简单的lambda函数如下:

int main()
{[]{}; //最简单的lambda表达式,该lambda函数不能做任何事情。return 0;
}

参数列表和返回值类型以及函数体都没什么,相信你也会有疑问,这个捕捉列表到底是个什么东西?

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

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

说明一下:

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

二、【lambda表达式的使用】

学会了lamda表达式的语法,再让我们学学它的使用,假设我们现在要交换两个数,我们应该怎样用lamda表达式来解决呢?

主要有以下几种做法:

1、【标准写法】

参数列表中包含两个形参,表示需要交换的两个数,注意需要以引用的方式传递。比如:

int main()
{int a = 10, b = 20;auto Swap = [](int& x, int& y)->void{int tmp = x;x = y;y = tmp;};Swap(a, b); //对lamda表达式进行调用,从而交换a和breturn 0;
}

说明一下:

  • lambda表达式是一个匿名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量,此时这个变量就可以像普通函数一样使用。
  • lambda表达式的函数体在格式上并不是必须写成一行,如果函数体太长可以进行换行,但换行后不要忘了函数体最后还有一个分号。

2、【利用捕捉列表进行捕捉】

以引用的方式捕捉所有父作用域中的变量,省略参数列表和返回值类型。比如:

int main()
{int a = 10, b = 20;auto Swap = [&]{int tmp = a;a = b;b = tmp;};Swap(); //交换a和breturn 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和breturn 0;
}

说明一下: 实际当我们以[&][=]的方式捕获变量时,编译器也不一定会把父作用域中所有的变量捕获进来,编译器可能只会对lambda表达式中用到的变量进行捕获,没有必要把用不到的变量也捕获进来,这个主要看编译器的具体实现。

3、【传值方式捕捉?】

如果以传值方式进行捕捉,那么首先编译不会通过,因为传值捕获到的变量默认是不可修改的,如果要取消其常量性,就需要在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变量,与函数的传值传参是一个道理,因此这种方法无法完成两个数的交换。

4、【lambda表达式之间不能相互赋值】

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

  • 因为lambda表达式底层的处理方式和仿函数是一样的,在VS2019下,lambda表达式在底层会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>
  • 类名中的uuid叫做通用唯一识别码(Universally Unique Identifier),简单来说,uuid就是通过算法生成一串字符串,保证在当前程序当中每次生成的uuid都不会重复。
  • ambda表达式底层的类名包含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;
}

可以看到,就算是两个一模一样的lambda表达式,它们的类型都是不同的。

说明一下: 

编译器只需要保证每个lambda表达式底层对应类的类名不同即可,并不是每个编译器都会将lambda表达式底层对应类的类名处理成<lambda_uuid>,这里是以VS2019为例。

三、【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表达式时,也能看到类似的代码:

  • 借助auto将lambda表达式赋值给add2对象时,会调用<lambda_uuid>类的构造函数。
  • 在使用add2对象时,会调用<lambda_uuid>类的()运算符重载函数。

本质就是因为lambda表达式在底层被转换成了仿函数。

  • 当我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对()运算符进行重载,实际lambda函数体的实现就是这个仿函数的operator()的实现。
  • 在调用lambda表达式时,参数列表和捕获列表的参数,最终都传递给了仿函数的operator()

ambda表达式和范围for是类似的,它们在语法层面上看起来都很神奇,但实际范围for底层就是通过迭代器实现的,lambda表达式底层的处理方式和函数对象是一样的。


总结

本篇博客到这里就结束了,相信你对lamda表达式已经有了清晰的认识,感谢观看!

........................................................其实我给你的爱比你想的多,其实我爱你比你想的多得多

                                                                                                                     ————《其实》

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

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

相关文章

一些有趣的整人小病毒

windows的小病毒 第一个占用资源&#xff08;最简单的病毒&#xff09; 打开一个文本文件&#xff0c;输入%0|%0&#xff0c;然后就命名为xxx.bat&#xff0c;就可以把你的电脑CPU利用率大大地提高然后只能重启了 第二个无限弹出窗口&#xff08;也很简单&#xff09; 打开…

【树莓派 5B】Python 版本切换

【树莓派 5B】Python 版本切换 前言整体思路具体步骤常见问题Python 无法建立与 Python3 的软连接 前言 本文基于树莓派5B 32-bit 树莓派OS&#xff0c;以 Python-3.11.2 降级到 3.9.2 为例&#xff0c;总结了在树莓派上切换 Python 版本的步骤&#xff0c;帮助大家轻松完成 P…

CAS简介

#1024程序员节&#xff5c;征文# CAS是什么&#xff1f; CAS&#xff08;Compare And Swap&#xff09;&#xff0c;即比较与交换&#xff0c;是一种乐观锁的实现方式&#xff0c;用于在不使用锁的情况下实现多线程之间的变量同步。 CAS操作包含三个操作数&#xff1a;内存位…

Stability.AI 发布 SD3.5 模型,能否逆袭击败 FLUX?如何在ComfyUI中的使用SD3.5?

就在前天&#xff0c;Stability AI 正式发布了 Stable Diffusion 3.5版本&#xff0c;包括 3 款强大的模型&#xff1a; Stable Diffusion 3.5 Large&#xff1a;拥有 80 亿参数&#xff0c;提供卓越的图像质量和精确的提示词响应&#xff0c;非常适合在 1 兆像素分辨率下的专…

鸿蒙开发:走进stateStyles多态样式

前言 一个组件&#xff0c;多种状态下&#xff0c;我们如何实现呢&#xff1f;举一个很简单的案例&#xff0c;一个按钮&#xff0c;默认状态下是黑色背景&#xff0c;点击后是红色&#xff0c;手指放开后还原黑色。 我们自然而然的就会想到利用手势的按下和抬起&#xff0c;…

Python小游戏11——扑克牌消消看小游戏

首先&#xff0c;你需要确保已经安装了pygame库。如果还没有安装&#xff0c;可以使用以下命令进行 安装&#xff1a; bash pip install pygame 代码示例&#xff1a; python import pygame import random import sys # 初始化pygame pygame.init() # 设置屏幕尺寸 SCREEN_WIDT…

美课+, 一个公司老项目,一段程序猿的技术回忆

前言 "美课"项目从2018年3月26号开始启动到2018年6月8号结束,总计两个月多的时间,项目的时间节点比较紧张.虽然最后没有上线很遗憾,但是,不管是在流程和项目上,对自己都是一次不错的尝试.下面我就对这次项目做一下iOS端的整体总结. #### 技术难点 *** 在iOS端,我感到…

鸿蒙应用开发:数据持久化

最近在搞公司项目用到了鸿蒙端的数据持久化&#xff0c;特来跟大家分享一下。 在鸿蒙开发中&#xff0c;可以使用以下几个包来实现数据的持久化处理&#xff1a; Data Ability 通过数据能力组件&#xff0c;开发者可以实现复杂的数据操作&#xff0c;包括增、删、改、查等功…

【国潮来袭】华为原生鸿蒙 HarmonyOS NEXT(5.0)正式发布:鸿蒙诞生以来最大升级,碰一碰、小艺圈选重磅上线

在昨日晚间的原生鸿蒙之夜暨华为全场景新品发布会上&#xff0c;华为原生鸿蒙 HarmonyOS NEXT&#xff08;5.0&#xff09;正式发布。 华为官方透露&#xff0c;截至目前&#xff0c;鸿蒙操作系统在中国市场份额占据 Top2 的领先地位&#xff0c;拥有超过 1.1 亿 的代码行和 6…

中间件详解与应用场景

1. 引言 随着信息技术的飞速发展&#xff0c;应用系统变得日益复杂&#xff0c;软件架构逐步从单体应用演变为分布式系统。在这种复杂的环境中&#xff0c;如何使各个系统、服务和组件之间顺畅地通信、协作&#xff0c;成为了软件开发中的关键问题。中间件&#xff08;Middlew…

Linux如何安装“ServerAgent“并使用?

1、cd /home/ 2、上传文件到项目文件下 3、解压 unzip ServerAgent-2.2.3.zip 4、打开文件 cd ServerAgent-2.2.3/ 5、赋权&#xff08;测试环境&#xff09; chmod -R 777 *6、启动 ./startAgent.sh

笔记整理—linux网络部分(3)socket接口

首先&#xff0c;send()函数和write()可以用于发送&#xff0c;而recv()和read()k可用于接收文件&#xff0c;其本质就是因为linux中&#xff0c;一切皆是文件。 int socket(int domain, int type, int protocol); domain是指域&#xff0c;是ipv4还是ipv6&#xff1b;type是s…

Prompt-Tuning方法学习

文章目录 一、背景1.1 Pre-training1.2 Fine-Tuning1.3 高效微调&#xff08;SOTA PEFT&#xff09;1.4 基于强化学习的进阶微调方法&#xff08;RLHF&#xff09; 二、Prompt-Tuning技术2.1 发展历程2.2 Prompt模板构建方式 三、基于连续提示的Prompt Tuning四、Q&A 一、背…

程序员节日的日期是10月24日‌程序员日

‌程序员节日的日期是10月24日。‌ 这一天被称为‌中国程序员日或‌1024程序员节&#xff0c;由‌博客园、‌CSDN等自发组织设立&#xff0c;旨在纪念程序员对科技世界的贡献。 程序员节日的由来和意义 1024程序员节的由来可以追溯到2010年&#xff0c;最初由网友提出设立一个…

如何制作一个自己的网站?

在今天的互联网时代&#xff0c;网站展示已经是一个很基础的营销工具。不管是企业、还是个人&#xff0c;如何制作一个自己的网站&#xff1f;本文将会提供一个全面的基础制作网页教程&#xff0c;教你如何从零开始制作网页。 网页制作的基础知识&#xff1a;HTML、CSS和JavaS…

RocketMQ消息处理详解!

文章目录 引言同步发送原理分析优缺点优点缺点 使用场景 异步发送原理分析优缺点优点缺点使用场景 单向发送原理分析优缺点优点缺点 使用场景 三种方式对比如何选择同步发送异步发送单向发送 总结 引言 在 RocketMQ 中&#xff0c;有 3种简单的消息发送方式&#xff1a;同步发…

R语言绘图——坐标轴及图例

掌握坐标轴与图例的设置与调整&#xff0c;对于提升数据可视化的清晰度和可读性至关重要。通过这些工具&#xff0c;可以有效地传达数据背后的故事&#xff0c;提高图表的表现力。 0x01 坐标轴 一、坐标轴的设置 1、修改坐标轴的标签 在ggplot2中&#xff0c;坐标轴是根据数…

计算服务器:开启科学计算新变革的强大引擎

1983 年&#xff0c;著名数学家 Lax 为首的调研小组指出&#xff0c;大型科学计算对国家安全、科技进步与经济发展至关重要&#xff0c;从美国国家利益出发&#xff0c;大型计算的绝对优势不容动摇。 科学计算是什么&#xff1f;为何在 20 世纪 80 年代就被提升到美国国家利益层…

Pytest日志收集器配置

前言 在pytest框架中&#xff0c;日志记录&#xff08;logging&#xff09;是一个强大的功能&#xff0c;它允许我们在测试期间记录信息、警告、错误等&#xff0c;从而帮助调试和监控测试进度。 pytest与Python标准库中的logging模块完美集成&#xff0c;因此你可以很容易地在…

vmware虚拟机linux系统安装

一、下载linux镜像安装包 步骤1---网址地址下载镜像 地址&#xff1a;Index of /ubuntu-releases/22.04/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 步骤2---下载linux版本号 步骤3---查看下载的linuxiso linux镜像操作系统 二、vmware新建安装linux操作系统…