C++相关概念和易错语法(29)(lambda、function、bind)

news2024/11/14 6:16:14

1.lambda

lamba表达式本质是匿名函数

书写格式:[ 捕捉列表 ] + ( 参数 ) + mutable + -> 返回值 + { 函数体 }

下面我会由简到难分享lambda的用法和它的理解

(1)基本使用和理解

我们先来看一段代码

我认为第一次看这个表达式应该还是能够看懂,但是里面的语法细节需要一条一条解释

这个时候疑问就更多了,为什么只能用auto?哪里体现出匿名了,fun不是有名字吗?

现在可以解释的是lambda表达式本质上是一个类,而这个类的名字我们是不知道的auto fun = 这个操作其实是一种拷贝构造,我们也可以理解为给这个类起了一个新名字,后续要使用这个类就得用这个新名字去操作,功能和原来的类是一样的。

通过我们之前的学习便可猜出,fun(m, n)这种操作是调用仿函数的operator(),现在我们知道这一点就足够了,后续我们会不断深入。

(2)传值捕获

在使用lambda的函数体中我们可以使用传递过来的参数和全局域的对象(注意同名时优先局部变量,要避免同名),这点和函数是一样的,但lambda还可以使用捕捉列表的对象。捕捉列表捕捉的是当前域的其他变量,不能跨域

捕捉列表是异于函数的一种新用法,因为lambda表达式都是那种仅在当前域有作用,针对临时环境搭建的临时函数,所以捕捉列表让它能更好地在这种场景发挥优势,更好地利用该局部域的变量。并且捕捉列表是随着lambda写死的,不受传参影响。

捕捉和传参同样有两种方式,一种叫传值捕捉,一种叫传引用捕捉,上面演示的就是传值捕捉。

传值捕捉到的对象都会用const修饰,是不能修改的

如果要修改就必须加上mutable

如我演示那样,mutable加上以后我们就可以对捕获的对象修改值了,不过注意我们是传值捕获,和传值传参一样都是开辟了一块新的空间单独存的,所以lambda内部的修改不会影响实际被捕获对象的值(要慎重使用,因为名字都一样很容易搞混)。

(3)传引用捕获

在捕获列表中,只要变量前加上&都叫做传引用捕获,如&a,虽然&a一般还有取地址的含义,但在捕捉列表中还是很好区分的。

从上图中我们要注意细节,首先是不需要加mutable了,即传引用捕捉没有const修饰,因为引用的功能是取别名,不会修改名字的属性。这就意味着我们可以直接在匿名函数体内修改变量的值,并影响外面了,如图中的m受到了修改。

传值捕获和传引用捕获还有两个快捷操作:[=]所有值传值捕捉,[&]所有值引用捕捉。

也可以[=, &a](混合捕获)针对a传引用捕获,其余的传值捕获,随机搭配均可,可以帮我们灵活捕获。注意[=, &a]可行,[&a, =]不可行,顺序这点细节要抓住

需要注意的是[=]虽然在语法上是所有值传值捕捉,但在编译器层面只会传调用了的变量,编译器或尽可能优化掉不需要的性能开销,这一点我们表面感知不到

(4)lambda底层——类

前面我们已经铺垫过,lambda的底层是一个类。这个类的名字是编译器自动生成的,格式为:lambda_UUID唯一识别码。在编译之前,我们都不可能知道这个类的名字。当我们想要用ret来接收这个lambda表达式时,就只能用auto类型(返回的是一个类,类的名字不知道,只能用auto推导)。每个lambda都对应唯一的UUID唯一识别码,就算里面实现的内容一样,但UUID唯一识别码不会相同。

既然lambda底层是类,那么所谓的函数参数,捕捉列表等都有各自对应的类的语法。其中,fun(1, 2)是调用类的operator(),函数参数列表对应的是operator()的参数列表。捕捉列表对应的是成员变量,捕捉的本质是初始化成员变量。

(5)lambda的省略写法

lambda表达式捕捉列表和函数体任何情况下都不能省略,其余的在特定情况下都能省略。

返回类型可以自动推导,只要写了return都可以自动推导,可以不写;无参可以不写();mutable按需写

不写return均默认空返回,即void

2.function包装器(适配器)

auto ret = lambda表达式其实已经传递出了一种包装的思想。lambda类型复杂不确定,那我们就使用ret给它包装一层,如果后续要调用的话直接去找外层的包装ret即可。function进一步延续了这样的思想。

function包装的是一切能用函数的形式去调用的对象,包括函数指针(函数名),仿函数以及lambda。这三种对象都有很明显的函数的特征。

包装形式:function  <  void  (  char,  int  )  > f1   =   Fun;

(1)包装函数指针、lambda、仿函数

其中,void是函数的返回值,括号内的char和int是函数参数,f1是这个包装器的名字,赋值号=后面可以跟函数指针(函数名)、仿函数实例化出的对象名、lambda表达式(auto ret = lambda表达式中的ret,也可以直接跟lambda表达式一次性包装到位)。我们发现通过这种方式我们可以将所有返回值和参数相同的函数相关的对象分成一类

仿函数的使用需要稍微注意,首先赋值号后必须要跟实例化出的对象的名字,函数指针、lambda都是要实例化出对象才能包装;其次,operator()必须是非static的public成员

反之在不知道具体函数的情况下也可以先用包装器占位

我们发现包装器和函数指针有相似之处,在C语言中就常用函数指针来实现包装。但C语言中函数出现的形式有限,在C++中仿函数、lambda的出现使得函数指针过于局限,如此包装器就是为解决这个问题出现的。

map<string, function<int(int, int)>> m就可以将key和对应的操作联系起来,在很多游戏中很实用。

(2)包装类的静态成员函数

静态成员函数也能包装,要指定类域,可以不加&,注意要包装静态成员函数要保证该成员函数是在public修饰下

由于类的非静态成员函数是在类的公共区域,所以直接指定类域即可

(3)包装类的非静态成员函数

这可以说是包装最难的一部分了,因为类的非静态成员函数是C++中函数最特殊的存在,第一个参数是this让包装变得很棘手。

第一种解决办法就是补上这个隐藏的参数

有一个小点要注意的是传&A::add,非静态成员函数必须加&,而且由于只是包装add这个函数指针,所以直接指定类域即可,a.add(1, 2)只能调用函数,没有传递指针的功能

但是如果仔细想,这里有个问题,如果f1(&a, 1, 2)的括号内的内容会直接去调用add函数的话,一定会报错,因为this不能显式传参。但为什么能跑通呢?

注意,function对类的非静态成员函数有特殊的处理。其它的像lambda、函数指针、类的静态成员函数都是直接调用对应函数的operator(),而非静态成员函数不是这样。

首先,对于非静态成员函数,会先调用function的operator(),function会识别第一个参数,这里传的是指针A*,实例化的对象是&a,将这个指针作为function的成员变量存起来。之后function会类似调用&a -> &A::add(1, 2),参数是除开第一个参数的剩余参数,如此就能顺利调用了。从这个过程中我们也可以意识到,类的非静态成员函数的调用必须要先实例化对象,不然function无法处理。

从这个原理中,我们发现,关键的点在于function中途处理了一次,利用我们所传的第一个参数来调用赋值号后的函数指针。function会自动判断调用方式。由此一来,另外一种处理方式就不难理解了。

同理,function会用第一个参数A()使用.的方式调用&A::add,参数是从我们所传参数的第二个算起。调用方式和类型判断是function的事,我们不用关心。

3.bind

bind绑定是又一种包装,它同样作用于函数形式的对象。前面的function虽然在一定程度上包装了函数,但像非静态成员函数的包装依然不完美,它并不能消除this带来的影响。并且在函数参数的顺序上,有可能我们想要做出统一,但function也不支持,bind就能解决这个问题。

(1)命名空间placeholders

在<functional>里,有一个命名空间里声明的变量需要我们认识

从_1到_20,这些都被称为占位符

当使用using placeholders::_1;时,_1会被引入该全局域并定义,也就是说,_1是一个全局变量,它的生命周期贯穿整个程序。同理,当使用using namespace placeholders;时,placeholders里面20个占位符都会引入全局域并定义。

function是一个模板类,而bind是一个模板函数,但这个模板函数的返回值和lambda一样不可预见,但它可以直接被function接收,所以bind常和function连用

基本形式:bind(  fun ( 可调用对象 ) , _1 , _2  )

这里我们可以看到,f1调用的顺序反过来了,这就是bind对传参顺序的调整。

我们从更复杂的情况来分析,这样更加全面

简单来说,调用处按顺序匹配占位符,bind处按顺序调用函数

在bind中,我们还可以将某个位置用具体值占领,这个时候function里面参数的个数要改,实际传参也要改,调用规则不变

如果添加了占位符,一定要修改参数个数和传参,不然多余的参数会被直接舍弃,遇上类型不匹配还要报错,情况太复杂,不建议深究,也完全没有意义。

在上面的基础之上,我们就可以先对函数进行第一步包装,再由function统一,其中最大受益者莫过于非静态函数的成员函数

通过bind,我们还可以直接绑定第一个参数为匿名对象A(),实际传参的过程是看bind后面的参数,而不是调用处和function处,因此虽然function的参数处没写,但A()是会传过去的,因此能够正常调用。

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

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

相关文章

LeetCode.55.跳跃游戏(贪心算法思路)

题目描述&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 输…

为什么大公司不愿意使用 Python 作为 Web 后端开发语言?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

使用uart串口配置TMC2209模块

串口配置的优点&#xff1a; 通过串口助手配置TMC2209的寄存器实现转速&#xff0c;方向&#xff0c;细分数等寄存器设置。最大细分可达256。 TMC2209串口配置数据发送格式&#xff1a; 通过数据手册可知&#xff0c;TMC2209写入数据需要发送64位16进制。 前面0~7位是同步保…

OpenStack云计算平台实战

项目一 任务一 了解云计算 目前主流的开源云计算平台如下&#xff1a; OpenStack。OpenStack是一个提供IAAS开源解决方案的全球性项目&#xff0c;由Rackspace公司和NASA共同创办&#xff0c;采用了Apache2.0许可证&#xff0c;可以随意使用。OpenStack并不要求使用专门的硬…

“走进孔子博物馆 赓续千年乐章”孔乐古筝专场音乐会圆满成功!

2024年8月11日下午&#xff0c;备受期待的“走进孔子博物馆 赓续千年乐章”孔乐古筝专场音乐会在孔子博物馆隆重举行。此次音乐会不仅吸引了众多古筝艺术爱好者前来观看&#xff0c;更是一次将中华传统文化精髓与现代艺术相结合的盛会。现场座无虚席&#xff0c;观众朋友们共同…

C语言基础(十三)

指针的用法&#xff1a; 测试代码1&#xff1a; #include "date.h" #include <stdio.h> #include <stdlib.h> int main() { int time getTime();int n 3, m 4; // 使用malloc函数动态分配n行的指针数组&#xff0c;并将其赋值给二级指针arr。…

链表的插入操作——CSP-J1真题详解

【题目】 假设有一个链表的节点定义如下: struct Node { int data; Node* next; }; 现在有一个指向链表头部的指针&#xff1a;Node* head。如果想要在链表中插入一个新的节点&#xff0c;其成员 data 的值为 42&#xff0c;并使新节点成为链表的第一个节点&#xff0c;下面…

LLM和VLM算法常见面试题

LLM相关问题&#xff1a; Bert和GPT的区别 Bert和GPT是两种不同类型的自然语言处理&#xff08;NLP&#xff09;模型&#xff0c;具有一些重要的区别。 模型架构&#xff1a;Bert是基于Transformer架构的模型&#xff0c;它是一个预训练的双向编码器。GPT也是基于Transformer…

【最大的和】

题目 思路 最大序列和的一般做法 dp前缀最大值 dp尾缀最大值 枚举分界点 代码 #include <bits/stdc.h> using namespace std;const int N 5e410; int a[N]; int um[N], dm[N], s; int main() {int t;cin >> t;um[0] INT_MIN;while(t--){int n;cin >> …

【Linux操作系统】进程间通信(1)

目录 一、认识进程间通信二、匿名管道三、命名管道 一、认识进程间通信 进程间不能直接传递数据&#xff0c;因为进程具有独立性&#xff0c;直接传递会破坏进程的独立性。 进程间通信是什么&#xff1f; 一个进程把自己的数据交给另一个进程。 为什么要有进程间通信&#xf…

ThreadLocal解惑

目录 1、ThreadLocal是什么? 2、ThreadLocal实现原理 3、设置线程变量的2种方式 4、关于ThreadLocal的内存泄漏问题 5、使用过程中的注意事项和误区 1、ThreadLocal是什么? 比较书面的回答&#xff1a; 类如其名&#xff0c;线程本地变量。当使用 ThreadLocal 维护变量时…

防爆巡检机器人:工业安全领域的璀璨明星

在当今快速发展的工业领域&#xff0c;安全与效率是企业追求的双核动力。特别是在石油、化工、钢铁冶金、燃气等高风险、高爆炸性的行业中&#xff0c;如何确保生产环境的绝对安全&#xff0c;同时提升巡检效率&#xff0c;成为了企业亟需解决的重大课题。正是在这样的背景下&a…

leetcode 438 找到字符串中所有字母异位词

leetcode 438 找到字符串中所有字母异位词 正文 正文 本题和 leetcode 49 字母异位分词 有些类似&#xff0c;只是 49 题中要求我们找出所有的异位词并进行存储&#xff0c;而本题我们只需要找出异位词对应的索引值。因此&#xff0c;我们无需用到字典&#xff0c;只需使用列表…

理解线程 ID 和 LWP

序言 在不同的系统中&#xff0c;为了更好地管理用户可能会采取不同的编号。比如在学校的教务系统中&#xff0c;管理学生使用的是学号&#xff1b;但是在住宿系统中&#xff0c;为了更加方便的获取一个学生的寝室信息&#xff0c;可能会采取结合你是哪一栋&#xff0c;哪一层&…

MindSearch 部署的到 Hugging Face Space

和原有的CPU版本相比区别是把internstudio换成了github codespace。 随着硅基流动提供了免费的 InternLM2.5-7B-Chat 服务&#xff08;免费的 InternLM2.5-7B-Chat 真的很香&#xff09;&#xff0c;MindSearch 的部署与使用也就迎来了纯 CPU 版本&#xff0c;进一步降低了部署…

【Windows】深度学习环境部署

引言 1 Windows环境准备 1.1 VSCode Visual Studio Code&#xff08;简称 VSCode&#xff09;是一款由微软开发的开源代码编辑器。它非常受开发者欢迎&#xff0c;因为它功能强大、扩展性好&#xff0c;并且支持多种编程语言。VSCode 尤其适合 Python 开发&#xff0c;特别是…

WEB渗透免杀篇-Pezor免杀

往期文章 WEB渗透免杀篇-免杀工具全集-CSDN博客 WEB渗透免杀篇-加载器免杀-CSDN博客 WEB渗透免杀篇-分块免杀-CSDN博客 WEB渗透免杀篇-Powershell免杀-CSDN博客 WEB渗透免杀篇-Python源码免杀-CSDN博客 WEB渗透免杀篇-C#源码免杀-CSDN博客 WEB渗透免杀篇-MSFshellcode免杀…

文心一言 VS 讯飞星火 VS chatgpt (331)-- 算法导论22.5 7题

七、给定有向图 G ( V &#xff0c; E ) G(V&#xff0c;E) G(V&#xff0c;E)&#xff0c;如果对于所有结点对 u , v ∈ V u,v∈V u,v∈V,我们有 u → v u→v u→v或 v → u v→u v→u&#xff0c;则 G G G是半连通的。请给出一个有效的算法来判断图 G G G是否是半连通的。证…

根据需求、质量属性描述和架构特性开发一套公路桥梁在线管理系统

目录 案例 【题目】 【问题 1】(12 分) 【问题 2】(13 分) 答案 【问题 1】答案 【问题 2】答案 相关推荐 案例 阅读以下关于软件架构评估的叙述&#xff0c;在答题纸上回答问题 1 和问题 2。 【题目】 某单位为了建设健全的公路桥梁养护管理档案&#xff0c;拟开发一套公…

若依框架搭建

一、后端启动 1、git克隆下载前后端分离版本 RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分离权限管理系统&#xff0c;同时提供了 Vue3 的版本 (gitee.com) 2、初始化项目 到springboot后如…