| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++ lambda 表达式深剖 -> 正文阅读 |
|
[C++知识库]C++ lambda 表达式深剖 |
传统艺能😎小编是双非本科大一菜鸟不赘述,欢迎米娜桑来指点江山哦(QQ:1319365055) 🎉🎉非科班转码社区诚邀您入驻🎉🎉 概念🤔自 C++11 开始,C++ 有三种方式可以创建/传递一个可调用的对象:
lambda 表达式本质上就是一个匿名函数,它是一个强大的功能,他的使用可以简化代码,而且可以提高代码可读性 这里举一个实例,以一个物品为例:
如果要对若干对象分别按照价格和数量进行升序、降序排序。 首先想到可以使用 sort 函数,但由于这里待排序的元素为自定义类型,因此需要用户自行定义排序时的比较规则,要控制 sort 比较方式有常见的两种方法,一是对商品类的的 () 运算符进行重载,二是通过仿函数来指定比较方式。 重载当前类的 () 运算符是不可行的,因为这里要求分别按照价格和数量进行升序、降序排序,每次排序都去修改一下比较方式是很低效且笨重的做法 比如我用仿函数的方式进行比较:
如你所见,仿函数也顺利解决了以上问题,但是如果定义和使用位置隔的很远就不好观察,这就要求取名的时候通俗易懂,这种情况下就更加推荐使用 lambda 表达式。 比如我这里有一个做加法的仿函数:
我们把这个加法仿函数改写成 lambda 表达式就是:
因为 lambda 表达式是一个匿名函数,该函数无法直接调用,这里我们借助 auto 将其赋值给一个变量,此时这个变量就可以像普通函数一样使用 这前后一对比,显而易见两者的优劣就高下立判了 语法🤔lambda 表达式定义:
当然在书写格式上并不是必须写成一行,如果函数体太长可以进行换行 capture-list:捕捉列表。上面的例子 auto Plus = [](int a, int b) { return a + b; }; 就没有捕获任何变量 没说明的就暂时不必理解,其中 除了捕获列表, L a m b d a 表达式的其它地方其实和普通的函数基本一样 \color{red} {除了捕获列表,Lambda 表达式的其它地方其实和普通的函数基本一样} 除了捕获列表,Lambda表达式的其它地方其实和普通的函数基本一样。lambda 参数列表和返回值类型都是可有可无的,但捕捉列表和函数体是不可省略的,因此最简单的lambda函数如下:
捕获方式🤔Lambda 表达式最基本的两种捕获方式是:按值捕获和按引用捕获 我们对捕获列表的说明描述了上下文中哪些数据可以被 lambda 函数使用,以及使用的方式是传值还是传引用
实际当我们以 [&](引用传递全捕获) 或 [=] (值传递全捕获)的方式捕获变量时,编译器也不一定会把父作用域中所有的变量捕获进来,编译器可能只会对 lambda 表达式中用到的变量进行捕获,实际要看编译器的具体实现 父作用域就是包含 lambda 表达式的语句块,语法上捕捉列表可由多个逗号分割的捕捉项组成,比如KaTeX parse error: Expected '}', got '&' at position 18: …olor{red} {[=, &?a, &b]} ,我们需要清楚 3 点:
相互赋值😎lambda表达式之间不能相互赋值,就算是两个一模一样的也不行,这不邪门儿了嘛,为啥呢? 因为 lambda 表达式底层处理方式和仿函数是一样的(本文后面的底层原理部分有细谈),在VS下 lambda 表达式会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid> 类名中的uuid叫做通用唯一识别码,简单来说就是通过算法生成的一串字符串,它具有随机性和不重复性,保证在当前程序中每次生成不同的 uuid,因为 lambda 表达式底层的类名包含 uuid,这就保证了每个 lambda 表达式底层类名都是唯一的! 我们可以通过 t y p e i d ( 变量名 ) . n a m e ( ) \color{red} { typeid(变量名).name() } typeid(变量名).name()的方式来获取lambda表达式的类型来验证上述结论:
如你所见,就算是一模一样的 lambda 表达式,它们的类型也是不同的 mutable🤔在实际使用中,比如实现一个交换函数,我们用 lambda 表达式实现:
这里一眼就是传值捕获,但是真的可以吗?答案是 No,他的编译不会通过,因为传值捕获到的变量默认是不可修改的(const):
如果要取消其常量属性,就需要在 lambda 表达式中加上 mutable 像这样:
但由于是传值捕捉,lambda 表达式中对局部变量的修改不会影响本身的变量,与函数的传值传参是一个道理,因此这种方法无法完成交换功能。 底层原理🤔实际编译器在底层对于 lambda 表达式的处理方式,完全就是按照函数的方式处理的,函数对象就是我们平常所说的仿函数,就是在类中对 () 运算符进行了重载的类对象 我们编写了一个 Add 类,然后对 () 运算符进行了重载,因此 Add 类实例化出的 add1 对象就是函数对象,add1 可以像函数一样使用。接着写了一个 lambda 表达式,并借助 auto 将其赋值给 add2 对象,这时 add1 和 add2 都可以像普通函数一样使用
我们再通过反汇编对代码进行观察: 然后 lambda 表达式这边也是和函数的过程非常类似:在借助 auto 将 lambda 表达式赋值给 add2 对象时,会调用 <lambda_uuid> 类的构造函数,在使用add2对象时,会调用<lambda_uuid>类的()运算符重载函数 本质就是因为 l a m b d a 表达式在底层被转换成了仿函数 \color{red} {本质就是因为lambda表达式在底层被转换成了仿函数} 本质就是因为lambda表达式在底层被转换成了仿函数。 当我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对 () 运算符进行重载,实际 lambda 函数体的实现就是这个仿函数 operator() 的实现,在调用 lambda 表达式时,参数列表和捕获列表的参数,最终都传递给了仿函数的 operator()。 aqa 芭蕾 eqe 亏内,代表着开心代表着快乐,ok 了家人们。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 12:49:01- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |