C++笔记---lambda表达式

1. 简单介绍及语法

Lambda表达式是C++11引入的一种便捷的匿名函数定义机制。

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

lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。

Lambda表达式的基本语法

Lambda表达式的基本语法格式如下:

[capture_list](parameters) -> return_type { function_body }
  • capture_list捕获列表(不可省略),该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数用于指定Lambda表达式可以访问的外部变量,以及捕获的方式(按值或按引用)。
  • parameters参数列表(可以省略),定义传递给Lambda表达式的参数。如果不需要参数传递,则可以连同()⼀起省略
  • -> return_type可选的尾部返回类型指定(可以省略)。如果省略,则由函数体中的返回语句推断返回值。
  • function_bodyLambda表达式的函数体(不可省略),包含执行的代码,函数体为空也不能省略。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
#include<iostream>
using namespace std;int main()
{// ⼀个简单的lambda表达式auto add = [](int x, int y)->int {return x + y; };cout << add(1, 2) << endl;// 1、捕捉为空也不能省略// 2、参数为空可以省略// 3、返回值可以省略,可以通过返回对象自动推导// 4、函数题不能省略auto hello = []{cout << "hello World" << endl;return 0;};hello();int a = 0, b = 1;auto swap1 = [](int& x, int& y){int tmp = x;x = y;y = tmp;};swap1(a, b);cout << a << ":" << b << endl;return 0;
}

2. 捕捉列表

在lambda表达式中不可以直接使用外部变量,因为其本质上是一个函数,要使用其所在域内的变量,需要用捕捉列表进行捕捉。

捕捉的方式有:显式传值捕捉,显式传引用捕捉,隐式传值捕捉,隐式传引用捕捉。

显式传值捕捉与传引用捕捉

第一种捕捉方式是在捕捉列表中显式的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。

直接将变量写到捕捉列表中时为传值捕捉,加上 "&" 则表示传引用捕捉:

[x,y,&z] 表示x和y值捕捉,z引用捕捉。

注意:传值捕捉的变量是被const修饰的,不可修改。

int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;auto func1 = [a, &b]{// 值捕捉的变量不能修改,引用捕捉的变量可以修改//a++;b++;int ret = a + b;return ret;};cout << func1() << endl;return 0;
}
隐式传值捕捉与传引用捕捉

"[=]" :捕捉列表中传入 "=" 表示自动传值捕捉在函数体内被用到的变量;

"[&]":捕捉列表中传入 "&" 表示自动传引用捕捉在函数体内被用到的变量。

int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 隐式值捕捉// 用了哪些变量就捕捉哪些变量auto func2 = [=]{int ret = a + b + c;return ret;};cout << func2() << endl;// 隐式引用捕捉// 用了哪些变量就捕捉哪些变量auto func3 = [&]{a++;c++;d++;};func3();cout << a << " " << b << " " << c << " " << d << endl;return 0;
}
 混合使用显式捕捉和隐式捕捉

在捕捉列表中混合使用隐式捕捉和显式捕捉:

[=,&x] 表示其他变量隐式值捕捉,x引用捕捉;

[&, x, y] 表示其他变量引用捕捉,x和y值捕捉。

当使用混合捕捉时,第一个元素必须是&或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉同理=混合捕捉时,后面的捕捉变量必须是引用捕捉

int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 混合捕捉1auto func4 = [&, a, b]{//a++;//b++;c++;d++;return a + b + c + d;};func4();cout << a << " " << b << " " << c << " " << d << endl;// 混合捕捉2auto func5 = [=, &a, &b]{a++;b++;/*c++;d++;*/return a + b + c + d;};func5();cout << a << " " << b << " " << c << " " << d << endl;return 0;
}
不能捕捉静态局部变量和全局变量

lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。

这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。

int x = 0;// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = [](){x++;};int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 局部的静态和全局变量不能捕捉,也不需要捕捉static int m = 0;auto func6 = []{int ret = x + m;return ret;};return 0;
}
mutable 修饰符

默认情况下, 传值捕捉的对象是被const修饰的,也就是说传值捕捉过来的对象不能修改。

mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。

使用该修饰符后,参数列表不可省略(即使参数为空)。

int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 传值捕捉本质是⼀种拷被,并且被const修饰了// mutable相当于去掉const属性,可以修改了// 但是修改了不会影响外面被捕捉的值,因为是⼀种拷贝auto func7 = [=]()mutable{a++;b++;c++;d++;return a + b + c + d;};cout << func7() << endl;cout << a << " " << b << " " << c << " " << d << endl;return 0;
}

3. lambda表达式的应用

在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。

lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的,以后我们会不断接触到。

例如在下面的例子中,将lambda表达式直接作为可调用对象传给sort函数,不仅省去了仿函数的定义,而且将比较逻辑与sort函数绑定到一起,提高了可读性。

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表达式生成一个仿函数的类,并返回该类的对象。

仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体。

lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。

在下面这个例子中,r1和r2除了类名不同以外,是完全等价的。

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;// 函数对象Rate r1(rate);// lambdaauto r2 = [rate](double money, int year) {return money * rate * year;};r1(10000, 2);r2(10000, 2);return 0;
}

我们可以使用编译器的转到反汇编的功能来看二者的底层是否一样:

	r1(10000, 2);
00007FF71CBD1A18  mov         r8d,2  
00007FF71CBD1A1E  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF71CBDBDA8h)]  
00007FF71CBD1A26  lea         rcx,[r1]  
00007FF71CBD1A2A  call        Rate::operator() (07FF71CBD1172h)  
00007FF71CBD1A2F  nop  r2(10000, 2);
00007FF71CBD1A30  mov         r8d,2  
00007FF71CBD1A36  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF71CBDBDA8h)]  
00007FF71CBD1A3E  lea         rcx,[r2]  
00007FF71CBD1A42  call        `main'::`2'::<lambda_1>::operator() (07FF71CBD1EA0h)  
00007FF71CBD1A47  nop  

可以看到,二者都调用了 "operator()" 。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记:黑马程序员JavaWeb开发教程(2024.11.9)

9.1 Mybatis-基础操作-环境准备 这里也没做&#xff0c;到时候写案例&#xff0c;如果需要环境配置什么的&#xff0c;可以看看这个 9.2 Mybatis-基础操作-删除 删除需要动态获取需要删除的id&#xff0c;使用方法传参&#xff0c;#{}的方式实现 在编写的delete方法中&a…

[Docker#3] LXC | 详解安装docker | docker的架构与生态

目录 1.LXC容器操作 安装LXC LXC容器操作步骤 2.理论 LXC 是什么&#xff1f; Docker 是什么 Docker 和虚拟机的区别 Docker 和 JVM 虚拟化的区别 Docker 版本 ⭕Docker 官方网站&#xff08;建议收藏&#xff09; Docker 架构 生活案例 Docker 生态 Docker 解决…

Spark的学习-02

Spark Standalone集群的安装 架构&#xff1a;普通分布式主从架构 主&#xff1a;Master&#xff1a;管理节点&#xff1a;管理从节点、接客、资源管理和任务 调度&#xff0c;等同于YARN中的ResourceManager 从&#xff1a;Worker&#xff1a;计算节点&#xff1a;负责利用自己…

白杨SEO:百度在降低个人备案类网站搜索关键词排名和流量?怎样应对?【参考】

很久没有写百度或者网站这块内容了&#xff0c;一是因为做百度网站朋友越来越少&#xff0c;不管是个人还是企业&#xff1b;二是百度上用户搜索与百度给到网站的流量都越来越少。 为什么想到今天又来写这个呢&#xff1f;因为上个月有个朋友来咨询我说网站百度排名全没了&…

一个怀旧,俺的第一个共享软件

今天网友说起了 福彩双色球的程序。俺就想起这个来了&#xff0c;这是俺的第一个共享软件&#xff0c;收入大约15000。在当时来说&#xff0c;速度算是最快的。有些地方用了汇编优化&#xff08;题外话&#xff0c;最近俺看到新闻&#xff0c;FFmpeg的作者也用汇编优化 性能提升…

QCustomPlot添加自定义的图例,实现隐藏、删除功能(二)

文章目录 QCustomPlot初识和基本效果图实现步骤:详细代码示例:实现原理和解释:使用方法:其他参考要实现一个支持复选框来控制曲线显示和隐藏的自定义 QCPLegend 类,可以通过继承 QCPLegend 并重写绘制和事件处理方法来实现,同时发出信号通知曲线的状态变更。 QCustomPl…

96.【C语言】存储体系结构

目录 1.金字塔图 2.形象理解的图 3.分析 4.推荐阅读 1.金字塔图 2.形象理解的图 3.分析 缓存的大小<<内存的大小 缓存分三级:速度:一级>二级>三级 在95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数文章遗留了一个问题,缓存命…

智能合约在供应链金融中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 智能合约在供应链金融中的应用 智能合约在供应链金融中的应用 智能合约在供应链金融中的应用 引言 智能合约概述 定义与原理 发展…

ODOO学习笔记(4):Odoo与SAP的主要区别是什么?

Odoo 和 SAP 都是知名的企业资源规划&#xff08;ERP&#xff09;软件&#xff0c;它们之间存在以下一些主要区别&#xff1a; Odoo与SAP的区别 一、功能特点 功能广度 Odoo&#xff1a;提供了一整套全面的业务应用程序&#xff0c;涵盖了销售、采购、库存管理、生产、会计、…

Leetcode 买卖股票的最佳时机 Ⅱ

使用贪心算法来解决此问题&#xff0c;通过在价格上涨的每一天买入并在第二天卖出的方式&#xff0c;累计所有上涨的利润&#xff0c;以实现最大收益。关键点是从第二天开始遍历&#xff0c;并且只要当前比前一天价格高&#xff0c;我们就在前一天买入然后第二天卖出去。下面是…

Unity常见问题合集(一)

PS&#xff1a;不定期更新...... 目录 &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff09; &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff0…

库打包工具 rollup

库打包工具 rollup 摘要 **概念&#xff1a;**rollup是一个模块化的打包工具 注&#xff1a;实际应用中&#xff0c;rollup更多是一个库打包工具 与Webpack的区别&#xff1a; 文件处理&#xff1a; rollup 更多专注于 JS 代码&#xff0c;并针对 ES Module 进行打包webpa…

软件工程 软考

开发大型软件系统适用螺旋模型或者RUP模型 螺旋模型强调了风险分析&#xff0c;特别适用于庞大而复杂的、高风险的管理信息系统的开发。喷泉模型是一种以用户需求为动力&#xff0c;以对象为为驱动的模型&#xff0c;主要用于描述面向对象的软件开发过程。该模型的各个阶段没有…

“高级Java编程复习指南:深入理解并发编程、JVM优化与分布式系统架构“

我的个人主页 接下来我将方享四道由易到难的编程题&#xff0c;进入我们的JavaSE复习之旅。 1&#xff1a;大小写转换------题目链接 解题思路&#xff1a; 在ASCII码表中&#xff0c;⼤写字⺟A-Z的Ascii码值为65- 90&#xff0c;⼩写字⺟a-z的Ascii码值为97-122。每个字 ⺟…

基于Zynq FPGA对雷龙SD NAND的性能测试评估

文章目录 一、SD NAND特征1.1 SD卡简介1.2 SD卡Block图 二、SD卡样片三、Zynq测试平台搭建3.1 测试流程3.2 SOC搭建 四、软件搭建五、测试结果六、总结 一、SD NAND特征 1.1 SD卡简介 雷龙的SD NAND系列有多种型号&#xff0c;本次测试使用的是CSNP4GCR01-AMW和CSNP32GCR01-A…

使用AT指令通过ESP8266实现TCP/IP服务器的创建、发送数据和接收数据

1. 初始化ESP8266 首先&#xff0c;确保ESP8266模块进入AT指令模式。 AT 2. 设置ESP8266为STA或APSTA模式 首先&#xff0c;确保ESP8266处于正确的模式。为了创建TCP/IP服务器&#xff0c;通常需要设置为STA模式&#xff08;连接到外部路由器&#xff09;或APSTA模式&#x…

Springboot+Vue+mysql前后端分离的Java项目部署教程

参考了网上许多文章&#xff0c;有的使用的是nginx&#xff0c;eclipse&#xff0c;其实只要是数据库或者java的软件基本都大同小异。 本人使用phpstudy对项目进行部署&#xff0c;亲测有效。 需要的软件&#xff1a; 1.Node.js安装&#xff08;ps&#xff1a;这一步我也不知道…

【MySQL】数据库整合攻略 :表操作技巧与详解

前言&#xff1a;本节内容讲述表的操作&#xff0c; 对表结构的操作。 是对表结构中的字段的增删查改以及表本身的创建以及删除。 ps&#xff1a;本节内容本节内容适合安装了MySQL的友友们进行观看&#xff0c; 实操更有利于记住哦。 目录 创建表 查看表结构 修改表结构 …

常用的c++新特性-->day03

断言和异常 断言断言的基本使用 静态断言静态断言的基本使用 异常异常基本使用c98异常案例 noexceptnoexcept简单案例 断言 断言的基本使用 #include <iostream> #include <cassert>// >>>>>>>>>>>>>>>> 断言的…

Python数据分析NumPy和pandas(二十七、数据可视化 matplotlib API 入门)

数据可视化或者数据绘图是数据分析中最重要的任务之一&#xff0c;是数据探索过程的一部分&#xff0c;数据可视化可以帮助我们识别异常值、识别出需要的数据转换以及为模型生成提供思考依据。对于Web开发人员&#xff0c;构建基于Web的数据可视化显示也是一种重要的方式。Pyth…