【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也不具有…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...