C++11新特性lambda的使用详解

article/2025/6/21 22:10:37

得益于C++11的发布,提供了提高效率的语法,C++11以后是现代C++,C++98是传统C++,这里来介绍lambda的使用和原理。

目录

1.lambda

2.列表捕捉

3,lambda的应用

4,lambda原理


1.lambda

lambda表达式本质是一个匿名函数对象,跟普通函数不一样,它们可以定义在函数内部。

lambda使用层而言它们没有类型。是编译器自动去分配的,所以我们用auto去接受lambda对象。

什么意思嘞?就是我们不知道lambda的返回类型,所以我们用auto让编译器自己去推到。

	auto pd = [](int a, int b)->int{ return a + b; };cout << pd(5, 3);

lambda表达形式,

//[]捕捉列表()参数列表->返回类型{}函数体;

[]捕捉列表不能省略,参数列表无参数可省略,返回类型明确,函数体不可以省略。

[]捕捉列表,该表总是出现在lambda开始的位置,编译器根据[]判断下列的函数是否为lambda函数,捕捉列表能够捕捉上下文的变量供lambda使用,分为传值捕捉,传引用捕捉。捕捉列表为空也不可以省略。为啥要捕捉呢?这是因为作用域的限制,普通函数想使用main函数中定义的参数必须进行传参,因为他们的作用域不同,lambda也是函数,如果不传参它不能使用函数作用域外的参数,所以用捕捉列表来捕捉供他使用。

()参数列表,和普通函数传参类似,如果不需要可以省略。

->返回类型,函数返回什么类型就写什么类型。

{}函数体,和普通函数类似,在函数体内除了可以使用参数,还可以使用所有捕捉到的变量,函数体为空也不能省略。

#include<iostream>
using namespace std;
int main()
{auto pd = [](int a, int b)->int{ return a + b; };//[]捕捉列表()参数列表->返回类型{}函数体;cout << pd(5, 3);return 0;
}

2.列表捕捉

lambda函数中我们了解[]是捕捉列表,里面是捕捉的参数,我们还提到分为传引用捕捉和传值捕捉。

lambda函数只能使用lambda函数体中的变量和参数,如果想使用外界的其它变量就需要用捕捉列表进行捕捉。

第一种捕捉方式是在[]中进行显示的传值捕捉和传引用捕捉,捕捉的多个变量用,分割。

[x,&y,z]xz是传值捕捉,传值捕捉的值默认被const修饰不能修改,但是它和外界的a是两份变量地址不一样,传引用捕捉的值可以被修改,修改传引用的值外界的变量也会被修改。

我们用代码来验证上面所说的两份a和传引用捕捉被修改。


int main()
{int c = 5;int d = 10;cout << "被修改前的d" << d<<endl;auto pd = [c, &d](int a, int b)->int {cout << "函数体c地址:" << &c << endl;; d--; return a + b; };//[]捕捉列表()参数列表->返回类型{}函数体;cout << pd(5, 3)<<endl;cout << "外界的c地址" << &c << endl;cout << "被修改后的d" << d;return 0;
}

我们可以看到传值捕捉的c和外界的c地址不一样,说明它两是两份a,类似的,传引用捕捉的b当它在函数体内进行d--后它在函数体外的d也被修改,说明它两的地址一样,同命相连。

第二种捕捉方式是隐式捕捉,这样[=]是传值捕捉,[&]是传引用隐式捕捉,区别是它会自动捕捉所有在函数体中用到的值,传值隐式捕捉不能被修改,传引用隐式捕捉可以被修改,意思是我们在lambda里用了哪些变量,它就会捕捉哪些变量。比如:

我们在函数体没有定义a,但是隐式捕捉之后它会返回5,所以我们看到它会自动捕捉函数体内用到的值。

int a = 5;
auto pd = [=]()->int { return a; };
cout << pd();

当我们修改代码后,在函数体内也创建a,它会返回函数体的a,这是就近原则。

第三种捕捉方式是混合捕捉,即隐式捕捉和显示捕捉一起用,[=,&x]这个的意思是隐式传值捕捉函数体内要用到的值,但是x值是单独传引用捕捉,其它的值都是传值捕捉。需要注意的是,当我们隐式传值捕捉之后就不能再这样写了[=,x]这在逻辑上是矛盾的,已经隐式捕捉要用到的值,又去传值捕捉x,两次传值捕捉,逻辑矛盾,报错。

传引用捕捉类似的[&,x]传引用隐式捕捉所有要用到的值,单独传值捕捉x,也是传引用隐式捕捉所以得值后不可以再次传引用捕捉,造成两次捕捉逻辑上的错误。也就是不能这么写[&,&x];\

lambda如果在函数局部域中,它可以捕捉lambda之前定义过的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda可以直接使用,这也就意味着lambda定义在全局域中[]内必须为空。

这里再解释一下,全局域变量和静态变量它们的作用范围是全局,生命周期是程序运行期间,内存地址固定而普通局部变量的生命周期仅仅是所在的作用域作用域也在当前作用域内,比如函数体内定义一个a变量,当函数栈帧销毁时,a变量也会随着一起销毁。我lambda定义在全局域,当要捕获它的时候要么该变量未创建,要么都被销毁了,全局变量又不用去捕获,所以定义在全局域的变量无需去捕获。

捕获的本质是传值捕获会拷贝一份变量供函数体使用,传引用捕获是直接拿到变量的地址来进行使用,传引用捕获的变量要注意生命周期,避免造成悬空引用。这里我们想一下普通的局部变量和全局变量,全局变量的生命周期是程序的整个运行期间,作用域是全局,局部变量的生命周期是在这个函数区间内,出函数就会被销毁,​​局部域可以访问全局变量,但全局域无法直接访问局部变量​​。如果lambda在全局域定义去捕获局部域变量可能导致未定义的行为,比如变量已经销毁,但是还去使用,这种行为未定义。对于全局域的变量大家都可以使用它无需去捕获。

默认情况下,传值捕捉到的变量是被const修饰的,要想取消常性,需要加mutable。

auto func7 = [=]()mutable{};

3,lambda的应用

在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的
类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对
象,既简单⼜⽅便。
lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定
制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到。
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 } };
// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
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._evaluate < g2._evaluate;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}

4,lambda原理

lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for
这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个
lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返
回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成
的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕
捉,编译器要看使⽤哪些就传那些对象。
上⾯的原理,我们可以透过汇编层了解⼀下,下⾯第⼆段汇编层代码印证了上⾯的原理。
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量
// 下⾯operator()中才能使⽤
00D8295C lea eax,[rate]
00D8295F push eax
00D82960 lea ecx,[r2]
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)
// 函数对象
Rate r1(rate);
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr [rate]
00D82970 movsd mmword ptr [esp],xmm0
00D82975 lea ecx,[r1]
00D82978 call Rate::Rate (0D81438h)
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D8298A movsd mmword ptr [esp],xmm0
00D8298F lea ecx,[r1]
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)


http://www.hkcw.cn/article/rFKkuZWlmb.shtml

相关文章

4000万日订单背后,饿了么再掀即时零售的“效率革命”

当即时零售转向价值深耕&#xff0c;赢面就是综合实力的强弱。 文&#xff5c;郭梦仪 编&#xff5c;王一粟 在硝烟弥漫的外卖行业“三国杀”中&#xff0c;饿了么与淘宝闪购的日订单量竟然突破了4000万单。 而距淘宝闪购正式上线&#xff0c;还不到一个月。 在大额福利优惠…

PostIn入门教程 - 使用IDEA插件快速生成API接口定义

PostIn是一款国产开源免费的接口管理工具&#xff0c;包含项目管理、接口调试、接口文档设计、接口数据MOCK等模块&#xff0c;支持常见的HTTP协议、websocket协议等。IDEA插件支持扫描代码自动生成接口文档并上传到PostIn系统。本文将详细介绍怎么安装IDEA插件&#xff0c;使用…

在RTX5060Ti上进行Qwen3-4B的GRPO强化微调

导语 最近赶上618活动&#xff0c;将家里的RTX 4060显卡升级为了RTX 5060Ti 16GB版本&#xff0c;显存翻了一番&#xff0c;可以进行一些LLM微调实验了&#xff0c;本篇博客记录使用unsloth框架在RTX 5060Ti 16GB显卡上进行Qwen3-4B-Base模型的GRPO强化微调实验。 简介 GPU性…

用户认证的魔法配方:从模型设计到密码安全的奇幻之旅

title: 用户认证的魔法配方:从模型设计到密码安全的奇幻之旅 date: 2025/05/31 09:34:15 updated: 2025/05/31 09:34:15 author: cmdragon excerpt: 用户认证体系的核心在于用户模型设计和密码安全规范。用户模型需包含唯一用户名、邮箱、加密密码等基础字段,使用SQLAlche…

Kafka ACK机制详解:数据可靠性与性能的权衡之道

在分布式消息系统中&#xff0c;消息确认机制是保障数据可靠性的关键。Apache Kafka 通过 ACK&#xff08;Acknowledgment&#xff09;机制 实现了灵活的数据确认策略&#xff0c;允许用户在 数据可靠性 和 系统性能 之间进行权衡。本文将深入解析 Kafka ACK 机制的工作原理、配…

ARM改口了,小米XRING O1真的是自研芯片

上周小米发布XRING O1芯片的时候&#xff0c;业内议论纷纷。有人说这不过是换个马甲的ARM方案&#xff0c;有人质疑小米的技术实力。但是这两天&#xff0c;ARM官方主动出来澄清了——小米的XRING O1确实没有使用ARM的CSS客户端平台解决方案。 这个转折挺有意思的。ARM作为IP授…

android 媒体框架之MediaCodec

一、MediaCodec 整体架构与设计思想 MediaCodec 是 Android 底层多媒体框架的核心组件&#xff0c;负责高效处理音视频编解码任务。其架构采用 生产者-消费者模型&#xff0c;通过双缓冲区队列&#xff08;输入/输出&#xff09;实现异步数据处理&#xff1a; 输入缓冲区队列…

浅谈 PAM-2 到 PAM-4 的信令技术演变

通信信令技术演进&#xff1a;从 PAM-2 到 PAM-4 在当今数字化高速发展的时代&#xff0c;数据传输需求呈爆炸式增长&#xff0c;行业对通信带宽的要求愈发严苛。为顺应这一趋势&#xff0c;通信信令技术不断革新&#xff0c;曾经占据主导地位的不归零&#xff08;NRZ&#xff…

(3)Playwright自动化-3-离线搭建playwright环境

1.简介 如果是在公司局域网办公&#xff0c;或者公司为了安全对网络管控比较严格这种情况下如何搭建环境&#xff0c;我们简单来看看 &#xff08;第一种情况及解决办法&#xff1a;带要搭建环境的电脑到有网的地方在线安装即可。 &#xff08;第二种情况及解决办法&#xf…

调用蓝耘Maas平台大模型API打造个人AI助理实战

目录 前言需求分析与环境配置明确需求环境准备选择合适的大模型 蓝耘Mass平台介绍API调用大模型API介绍API 调用流程 可交互AI助理开发总结 前言 大数据时代&#xff0c;个人隐私很难得到保障&#xff0c;如果我们需要借助大模型解决一些私人问题&#xff0c;又不想隐私被泄露…

智联未来:低空产业与AI新纪元-(下)

1. 隐形战场&#xff1a;全球规则制定权争夺战 低空经济的崛起&#xff0c;本质是数字主权的争夺战。当美国FAA将无人机适航认证周期延长至36个月&#xff0c;欧盟推出"天空云图"计划整合全境飞行数据时&#xff0c;中国正以制度创新构建自己的规则体系。 1.1 空域…

关于销售的几点注意事项

一、把客户当朋友聊 做买卖这事儿啊&#xff0c;说白了就是人和人打交道。您要是见着客户就背产品说明书&#xff0c;人家扭头就走。得学会听对方说话&#xff0c;琢磨他到底想要啥。就像您去菜市场买菜&#xff0c;摊主要是光说"这菜新鲜"&#xff0c;您可能没感觉…

C++语法系列之右值

前言 本来是想在C11里写这篇文章的&#xff0c;发现东西很多&#xff0c;就单独列一篇文章了&#xff0c; 右值这个概念是在C11中提出来的&#xff0c;以前只有左值和左值引用的概念&#xff0c;C11后提出了右值和右值引用&#xff0c;为什么要提出右值和右值引用&#xff1f;…

day17 常见聚类算法

目录 准备操作 聚类评估指标介绍 1.轮廓系数&#xff08;Sihouette Score&#xff09; 2.CH指数&#xff08;Calinski-Harabasz Index&#xff09; 3.DB指数&#xff08;Davies-Bounldin Index&#xff09; KMeans聚类 算法原理 确定簇数的方法&#xff1a;肘部法 KMeans算法的…

LCS 问题解释

最长公共子序列问题&#xff08;Longest Common Subsequence&#xff09;&#xff0c;该问题可以表述为&#xff0c;在 A , B A,B A,B 中找出一段子序列 x x x&#xff0c;使得 x x x 既是 A A A 的子序列&#xff0c;又是 B B B 的子序列。 你可以理解为&#xff0c;在两…

Windows最快速打开各项系统设置大全

目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找&#xff08;最慢&#xff09; 2.2 方法二cmd命令&#xff08;慢&#xff09; 2.3 方法三快捷键&#xff08;快&#xff09; 2.4 方法四搜索栏&#xff08;快&#xff09; 2.5 方法五任务栏&#xff08;最快&am…

OTSU算法原理与Python实现:图像二值化的自动阈值分割

1 引言 图像二值化是计算机视觉中的基础操作&#xff0c;它将灰度图像转换为黑白图像&#xff0c;常用于文档扫描、目标检测等任务。OTSU算法&#xff08;大津法&#xff09;是一种自动确定二值化阈值的算法&#xff0c;无需人工干预&#xff0c;通过最大化类间方差来分离前景和…

python:批量创建文件

#需求&#xff1a;在指定路径下批量创建3000#可以先弄个10个文本文件&#xff0c;文件格式为序号——物资类别——用户识别码组成 #1.序号从0001到3000 #2.物资类别包括&#xff1a;水果&#xff0c;烟酒&#xff0c;粮油&#xff0c;肉蛋&#xff0c;蔬菜 #3.用户识别码为9位的…

kafka学习笔记(三、消费者Consumer使用教程——配置参数大全及性能调优)

本章主要介绍kafka consumer的配置参数及性能调优的点&#xff0c;其kafka的从零开始的安装到生产者&#xff0c;消费者的详解介绍、源码及分析及原理解析请到博主kafka专栏 。 1.消费者Consumer配置参数 配置参数默认值含义bootstrap.servers无&#xff08;必填&#xff09;…

静态综合实验

题目 1.划分IP地址 因为所有网段基于192.168.1.0/24&#xff0c;所以需要自己进行合理的划分。如图&#xff0c;我已经划分完成。 2.启动 3.给五个路由器进行改名 4.给网关写入IP地址 R1 R2 R3 R4 5.完成网段的声明和环回接口的创建 6.在R1上进行ping&#xff0c;观察是否…