【C++11】Lambda 表达式:基本使用 和 底层原理
文章目录
- Lambda 表达式
- 1. 不考虑捕捉列表
- 1.1 简单使用介绍
- 1.2 简单使用举例
- 2. 捕捉列表 [ ] 和 mutable 关键字
- 2.1 使用方法
- 传值捕捉
- 传引用捕捉
- 2.2 捕捉方法一览
- 2.3 使用举例
- 3. lambda 的底层分析
Lambda 表达式
书写格式:
[capture_list](parameters) mutable -> return_type{statement}
[capture_list]:
- 捕捉列表,不能省略。
- 固定在 lambda 表达式开始的位置,编译器也是根据
[]
来判断接下来的代码是否为 lambda 函数。 - 捕捉列表能够捕捉上下文中的变量供 lambda 函数使用。
(parameters):
- 参数列表。
- 与普通函数的参数列表一致,如果不需要参数传递,则可以连同
()
一起省略。
mutable:
- 一个修饰符,取消传值捕捉时值的默认 const 属性(lambda 函数默认是一个 const 类型的,里面的值不可修改)。
- 另:若使用了 mutable 修饰符,则
(参数列表)
是不可省略掉的,即使是参数为空。
return_type:
- 返回值类型。可以省略,编译器会自动推导。
{statement}:
- 函数体部分,{} 不能省略,但内容可以为空。
- 在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
如下是最简单的 lambda 对象,没啥用就是了…
[] {};
1. 不考虑捕捉列表
1.1 简单使用介绍
比如我们要实现一个两个数相加的函数,用 lambda 表达式就需要写成这样
auto add = [](int x, int y)->int {return x + y; };
cout << add(1, 2) << endl;
//cout << [](int x, int y)->int {return x + y; }(1, 2) << endl; // 这样写也能运行,但是我们不这样...
解析:= 后面这一坨整体,代表的是一个 lambda 对象,拿这个对象去构造 add后面就可以用 add 去等价调用函数了
可以看出,lambda 表达式实际上可以理解为 匿名函数,该函数无法直接调用,如果想要直接调用,可借助 auto 将其赋值给一个变量。
需要注意的是:
- 返回值可以忽略(编译器自动完成推导)
- 函数体语句多的话,可以按照如下格式写
auto add = [](int x, int y) // 返回值可以省略,编译器可以自动推导
{ // 函数体语句多的话,可以放下来写return x + y;
};
cout << add2(1, 2) << endl;
1.2 简单使用举例
🌰实现商品各个内容的排序:
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
仿函数写法:
struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };// <sort(v.begin(), v.end(), ComparePriceLess());// >sort(v.begin(), v.end(), ComparePriceGreater());
}
lambda 写法:
书写格式:[capture - list](parameters) mutable -> return-type{ statement}
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };// 价格升序auto priceLess = [](const Goods& g1, const Goods& g2)->bool {return g1._price < g2._price; };sort(v.begin(), v.end(), priceLess); // 相较于仿函数更好调试// 价格降序sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._price > g2._price; }); // 这个把断点放头上可能会一直出不来,调试的时候跳到了需要手动取消断点走下一个// 改成比较别的类型也很方便sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._evaluate > g2._evaluate;});return 0;
}
2. 捕捉列表 [ ] 和 mutable 关键字
📕注意事项(具体论证见下文):
-
父作用域指包含lambda函数的语句块。
-
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
-
捕捉列表不允许变量重复传递,否则就会导致编译错误。
eg:[=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
-
在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。同时有,在块作用域以外的 lambda 函数捕捉列表必须为空。
-
lambda表达式之间不能相互赋值,即使看起来类型相同
若不使用 lambda 捕捉,实现一个 swap 接口如下:
int x = 0, y = 1;
auto swap1 = [](int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
};swap1(x, y);
cout << x << " "<< y << endl;
而 捕捉列表 描述了:
- 上下文中哪些数据可以被 lambda 使用
- 以及使用的方式 传值 还是 传引用
2.1 使用方法
传值捕捉
-
[val]
是 传值捕捉 [x, y],相当于把 x 和 y “捕捉” 到 lambda 表达式中,直接就可以访问了。这时 lambda 是 const 函数,其中的 x 和 y 不能修改; -
加上关键字
mutable
就可以修改了,不过此时的 x 和 y 就是函数形参。 -
另外需要注意的是,有 mutable 时,参数列表的括号不能省略。建议平时也不要省略
并不能达到效果的错误使用:
// err,这是一个错误的写法
int x = 0, y = 1;
auto swap2 = [x, y]() mutable
{int tmp = x;x = y;y = tmp;
};
swap2();
cout << x << " " << y << endl;
像如上,虽然对 x 和 y 进行了捕捉,也加上了 mutable 使其可以修改,但实际并达不到我们让全局变量 x 和 y 修改的效果。因为 lambda 中他们只是形参,一份临时拷贝的对象。
传引用捕捉
[&val]
是将外面的值传引用到 lambda 内部- 需要在 lambda 内部对某个变量修改时用传引用捕捉
真正修改了外面的参数:
// 这里的 &x 就是引用捕捉int& x(不是取地址
// 引用捕捉
int x = 0, y = 1;
auto swap2 = [&x, &y]()
{int tmp = x;x = y;y = tmp;
};
swap2();
cout << x << " " << y << endl;
2.2 捕捉方法一览
混合捕捉
[var]
:表示 值传递方式 捕捉变量 var
[this]
:表示 值传递方式 捕捉当前的 this 指针
[&var]
:表示 引用传递捕捉 变量 var
auto func1 = [&x, y]()
{//...
};
全部引用捕捉
[&]
:表示 引用传递捕捉 所有父作用域中的变量(包括 this)
auto func2 = [&]()
{//...
};
全部传值捕捉
[=]
:表示值传递方式捕获所有父作用域中的变量(包括 this)
auto func3 = [=]()
{//...
};
全部引用捕捉,x 传值捕捉
auto func4 = [&, x]()
{//...
};
此外排列组合:
[=, &a, &b]
:以 引用传递 的方式捕捉变量 a 和 b,值传递方式 捕捉其他所有变量
[&,a, this]
:以 值传递方式 捕捉变量 a 和 this,引用方式 捕捉其他变量
2.3 使用举例
先来个讲解前提,创建线程:
- Linux 下创建线程:pthread_create(posix)
- C++98,linux 和 windows 下都可以支持的多线程程序:条件编译。
#ifdef _WIN32CreateThread
#elsepthread_create
#endif
- C++11,linux 和 windows 下都可以支持的多线程程序:thread库。
🌰要求 m 个线程分别打印 1~n
线程的传统写法:
void Func1(int n, int num)
{for (int i = 0; i < n; i++){cout <<num<<":" << i << endl;}cout << endl;
}
int main()
{int n1, n2;cin >> n1 >> n2;thread t1(Func1, n1, 1);thread t2(Func1, n2, 2);t1.join();t2.join();return 0;
}
lambda 写法:第一种,较为冗余,不便于添加线程
int main() // 这个版本蛮冗余
{int n1, n2;cin >> n1 >> n2;thread t1([n1](int num){for (int i = 0; i < n1; i++){cout <<num<<":" << i << endl;}cout << endl;}, 1);thread t2([n2](int num){for (int i = 0; i < n2; i++){cout << num << ":" << i << endl;}cout << endl;}, 2);t1.join();t2.join();return 0;
}
lambda 写法:第二种,推荐
int main()
{size_t m;cin >> m;vector<thread> vthds(m);// 要求 m 个线程分别打印 1~nfor (size_t i = 0; i < m; i++){size_t n;cin >> n;vthds[i] = thread([i, n, m]() { // 匿名的lambda对象,移动赋值给的vhds[i]for (int j = 0; j < n; j++){cout << i << ":" << j << endl;}cout << endl;});}for (auto& t : vthds) // thread 不支持拷贝构造(delete了),这里要加引用才跑得动{t.join();}return 0;
}
3. lambda 的底层分析
先说结论,实际在底层编译器对于 lambda 表达式的处理方式,完全就是按照函数对象(仿函数)的方式处理的。
即:如果定义了一个 lambda 表达式,编译器会自动生成一个类,在该类中重载了 operator()。
- 参数列表 会变成 仿函数的参数
- 函数体 就是 仿函数主体
- lambda 对象的类型 就是 仿函数的类型(见后文)
- 如下这个仿函数类是一个没有给成员变量的空类,所以大小是 1 个字节 反汇编可以查到
int x = 0, y = 1;
int m = 0, n = 1;auto swap1 = [](int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
};cout << sizeof(swap1) << endl; // 输出 1
有如下一个模拟计算理财收益的类:
class Rate
{
public:Rate(double rate) : _rate(rate) {} // 传入利率double operator()(double money, int year) // 参数:本金和年限,返回收益{return money * _rate * year; // 模拟计算}
private:double _rate;
};
- 以下代码,函数对象的汇编过程:call 仿函数的构造函数,再 call operator()
// 函数对象
int main()
{double rate = 0.49;Rate r1(rate);r1(10000, 2);cout << sizeof(r1) << endl; // 8return 0;
}
-
以下代码,lambda 汇编过程:call
lambda_uuid
类的构造函数,再call <lambda_uuid>::operator() -
uuid 是一个电脑生成的唯一随机值,作为 lambda 类的后缀,刚刚好唯一标识
查看 r2 的大小,r2 里面没使用 lambda 涉及参数之外的 变量 n(如果用了的话根据内存对齐规则,r2 的大小是 16),也就是说:
- [=] 实际使用的值,才会在 lambda 类中作为成员变量初始化,所以这里的 8 是 double _rate 的大小
// lambda
int main()
{ double rate = 0.49;int n = 0; // 测试 [=] 全部传值捕捉auto r2 = [=](double money, int year)->double {return money * rate * year; };r2(10000, 2);cout << sizeof(r2) << endl; // 8return 0;
}// 所以:lambda 和范围 for 一样,底层是用别的实现的,被封装了一趟而已
- 之前提及的 lambda_uuid,才是 lambda 表达式底层的类型名称,编译器会给他们唯一标识的名称
下面代码,f1 和 f2 即使看着一样,如上阐述,实则底层的类型名称都不同,不能互相赋值:
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
//f1 = f2; // err...
🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~
相关文章:
【C++11】Lambda 表达式:基本使用 和 底层原理
文章目录 Lambda 表达式1. 不考虑捕捉列表1.1 简单使用介绍1.2 简单使用举例 2. 捕捉列表 [ ] 和 mutable 关键字2.1 使用方法传值捕捉传引用捕捉 2.2 捕捉方法一览2.3 使用举例 3. lambda 的底层分析 Lambda 表达式 书写格式: [capture_list](parameters) mutabl…...

【网络安全---ICMP报文分析】Wireshark教程----Wireshark 分析ICMP报文数据试验
一,试验环境搭建 1-1 试验环境示例图 1-2 环境准备 两台kali主机(虚拟机) kali2022 192.168.220.129/24 kali2022 192.168.220.3/27 1-2-1 网关配置: 编辑-------- 虚拟网路编辑器 更改设置进来以后 ,先选择N…...

【Docker】Docker的应用包含Sandbox、PaaS、Open Solution以及IT运维概念的详细讲解
前言 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 📕作者简介:热…...
Java Applet基础
Java Applet基础 目录 Java Applet基础 Applet的生命周期 "Hello, World" Applet: Applet 类 Applet的调用 获得applet参数 指定applet参数 应用程序转换成Applet 事件处理 显示图片 播放音频 applet是一种Java程序。它一般运行在支持Java的Web浏览器内。因…...

【记录】IDA|IDA怎么查看当前二进制文件自动分析出来的内存分布情况(内存范围和读写性)
IDA版本:7.6 背景:我之前一直是直接看Text View里面的地址的首尾地址来判断内存分布情况的,似乎是有点不准确,然后才想到IDA肯定自带查看内存分布情况的功能,而且很简单。 可以通过View-Toolbars-Segments,…...

LIMS实验室信息管理系统源码 基于计算机的数据处理技术、数据存储技术、网络传输技术、自动化仪器分析技术于一体
LIMS 是一个集现代化管理思想与基于计算机的数据处理技术、数据存储技术、网络传输技术、自动化仪器分析技术于一体,以实验室业务和管理工作为核心,遵循实验室管理国际规范,实现对实验室全方位管理的信息管理系统。 LIMS将样品管理、数据管理…...
有效括号相关
相关题目 20. 有效的括号 921. 使括号有效的最少添加 1541. 平衡括号字符串的最少插入次数 32. 最长有效括号 # 20. 有效的括号 class Solution:def isValid(self, s: str) -> bool:stack []for pare in s:if pare in ([{:stack.append(pare)if not stack or (pare ) and…...
浅谈泛型擦除
文章目录 泛型擦除(1)转换泛型表达式(2)转换泛型方法泛型擦除带来的问题 泛型擦除 在编码阶段使用泛型时加上的类型参数,会被编译器在编译阶段去掉,这个过程叫做泛型擦除。 泛型主要用于编译阶段。在编译后生成的Java字节码文件中不包含泛型中的类型信息…...

nodejs+vue校园跑腿系统elementui
购物车品结算,管理个人中心,订单管理,接单处理,商品维护,用户管理,系统管理等功育食5)要求系统运行可靠、性能稳定、界面友好、使用方便。 第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 10 3.2.1技术…...
Redis Cluster Cron调度
返回目录 说明 clusterCron 每秒执行10次clusterCron 内置了一个iteration计数器。每一次运行clusterCron,iteration都加1。当 iteration % 10 0的时候,就会随机选取一个节点,给它发送PING。而由于clusterCron每秒执行10次,所以…...
Redis Cluster Gossip Protocol: Message
返回目录 消息结构 消息头部消息数据(可选)extension(可选) 消息头部 字段定义 Signature: “RCmb” 这4个字符(Redis Cluster message bus 的简称)totalLen: 消息的总字节数version:当前为…...

【JVM】第四篇 垃圾收集器ParNewCMS底层三色标记算法详解
导航 一. 垃圾收集算法详解1. 分代收集算法2. 标记-复制算法3. 标记-清除算法4. 标记-整理算法二. 垃圾收集器详解1. Serial收集器2. Parallel Scavenge收集器3. ParNew收集器4. CMS收集器三. 垃圾收集底层三色标记算法实现原理1. 垃圾收集底层使用三色标记算法的原因?2. 垃圾…...

STM32复习笔记(四):独立看门狗IWDG
目录 (一)简介 (二)CUBEMX工程配置 (三)相关函数 总结: (一)简介 独立看门狗本质是一种定时器,其作用是监视系统的运行,当系统发生错误&…...
SpringBoot中常用注解的含义
一、方法参数注解 1. PathVariable 通过RequestMapping注解中的 { } 占位符来标识URL中的变量部分 在控制器中的处理方法的形参中使用PathVariable注解去获取RequestMapping中 { } 中传进来的值,并绑定到处理方法定一的形参上。 //请求路径:http://3333…...

学位论文的写作方法,较好的参考文章
摘要 结合2个文章: [1]程鑫. 网联环境下交通状态预测与诱导技术研究[D]. 长安大学, 2017. [2]吴昊. 关中平原水资源变化特征与干旱脆弱性研究[D]. 长安大学, 2018. 主要研究内容及技术路线 各章小结和引言的写作 [1]程鑫. 网联环境下交通状态预测与诱导技术…...

基于SpringBoot的科研工作量获奖项目管理平台设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...

嵌入式Linux应用开发-驱动大全-第一章同步与互斥④
嵌入式Linux应用开发-驱动大全-第一章同步与互斥④ 第一章 同步与互斥④1.5 自旋锁spinlock的实现1.5.1 自旋锁的内核结构体1.5.2 spinlock在UP系统中的实现1.5.3 spinlock在SMP系统中的实现 1.6 信号量semaphore的实现1.6.1 semaphore的内核结构体1.6.2 down函数的实现1.6.3 u…...

算法-数学-斜率-直线上最多的点数
算法-数学-斜率-直线上最多的点数 1 题目概述 1.1 题目出处 https://leetcode.cn/problems/max-points-on-a-line/ 1.2 题目描述 给你一个数组 points ,其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 2 暴力搜索斜率…...

项目进展(五)-修复PCB电路板,学习32位ADC芯片ADS1285
一、前言 上个月29号放假了,和朋友一起去了南京(人是真滴多),师兄晚放假几天,结果在测试时不小心把12V和GND碰触到一起了,导致12V短路,电路板几乎瘫痪了。 今天下午到学校之后就开始着手寻找问题和修复,最…...

(三) Markdown插入互联网或本地视频解决方案
前言 不论博客系统是WordPress还是Typecho,绕不开的是两种书写语言,一种称之为富文本,一种叫做Markdown。 Markdown有很多好处,也有很多坏处,比如Markdown本身不具备段落居中的功能,以及Markdown也不具有…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁
赛门铁克威胁猎手团队最新报告披露,数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据,严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能,但SEMR…...

spring boot使用HttpServletResponse实现sse后端流式输出消息
1.以前只是看过SSE的相关文章,没有具体实践,这次接入AI大模型使用到了流式输出,涉及到给前端流式返回,所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
这个警告表明您在使用Vue的esm-bundler构建版本时,未明确定义编译时特性标志。以下是详细解释和解决方案: 问题原因: 该标志是Vue 3.4引入的编译时特性标志,用于控制生产环境下SSR水合不匹配错误的详细报告1使用esm-bundler…...
【系统架构设计师-2025上半年真题】综合知识-参考答案及部分详解(回忆版)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7题】【第8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20~21题】【第…...

如何使用CodeRider插件在IDEA中生成代码
一、环境搭建与插件安装 1.1 环境准备 名称要求说明操作系统Windows 11JetBrains IDEIntelliJ IDEA 2025.1.1.1 (Community Edition)硬件配置推荐16GB内存50GB磁盘空间 1.2 插件安装流程 步骤1:市场安装 打开IDEA,进入File → Settings → Plugins搜…...