网站做3年/资阳市网站seo
文章目录
- 1. 概念
- 2. 语法
- 3. 示例
- 示例1
- 示例2
- 示例3
- 示例4
- 4. 捕捉方式
- 基本方式
- 隐式和混合
- 补充
- 5. 传递lambda表达式
- 示例
- 6. 原理
- 7. 内联属性
1. 概念
lambda表达式实际上是一个匿名类的成员函数,该类由编译器为lambda创建,该函数被隐式地定义为内联。因此,调用lambda表达式相当于直接调用它的operator()函数,这个函数可以被编译器内联优化(建议)。
例如快速排序算法,STL允许用户自定义比较方式,在C++11之前,通常使用仿函数实现。但是代码一旦很长,使用之处和仿函数实现的地方相隔甚远,而且如果仿函数的命名不规范,很容易造成使用上的困难。
仿函数,又叫做函数对象(functor,function object),因为实现仿函数的方式就是重载一个类的
operator()
,只是用起来跟函数一样,其本质依然是一个对象。
2. 语法
C++11的lambda表达式是一种允许内联函数的特性,它可以用于不需要重用和命名的代码片段。lambda表达式的一般形式是:
[capture clause] (parameters) mutable -> return-type { function body }
- [captureclause]:捕捉列表。该列表总是出现在lambda函数的开始位置,编译器根据
[]
来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。 - (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
- ->return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可以省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
以一个简单的例子理解lambda表达式的语法:
[]
{cout << "hello lambda" << endl;
}
这一大坨就表示它是一个类型/对象(type、class),我们知道,在类型后加上()
,就表示调用这个对象的构造函数,**而lambda的语法有些不同,在它后面加上括号,就表示调用它。**就像:
#include <iostream>
using namespace std;int main()
{[]{cout << "hello lambda" << endl;}();return 0;
}
输出:
hello lambda
而一般没有人会这样调用lambda表达式,因为这样还不如直接写中间的语句,所以可以暂时给它一个名字:
auto l = []
{cout << "hello lambda" << endl;
};
l在这里是任意取的名字(实际上要根据含义),刚才提到,方括号和后面一坨东西,就相当于一个对象,一般情况下我们是不知道它的类型的,所以通常用auto接收。
现在,最简形式的lambda表达式就写出来了,试着调用它:
#include <iostream>
using namespace std;int main()
{auto l = []{cout << "hello lambda" << endl;};l();return 0;
}
输出:
hello lambda
注意:
- 上面的例子和概念中的“内联”属性并没有关系。
- lambda表达式在main函数之外定义也不影响结果。
- lambda表达式的最后有分号。
- lambda函数的参数列表和返回值类型不是必要的,但捕捉列表和函数体是不可省略的。习惯上不写返回值,让表达式自动推导。
3. 示例
现在,lambda表达式的基本用法已经有大概的了解了,下面将介绍它的细节。
示例1
int main()
{int id = 0;auto f = [id] () mutable{cout << "id:" << id << endl;++id;};id = 42;f();f();f();cout << "id:" << id << endl;
}
注:在这里先不管mutable
。
[id]
表示在lambda表达式外部的所有名为id
的变量都能被使用()
表示这个lambda表达式不用传参- 函数内部的语句打印变量
id
的值且累加 - 在更新变量
id
的值为42后,调用3次lambda表达式f
- 最后再打印变量
id
的值
输出:
id:0
id:1
id:2
id:42
这与设想的结果应该不同:既然变量id
的值已经被更新为42,那么lambda表达式f
打印出的值应该是42、43、44,这是为何呢?这个问题会在看完所有例子以后揭晓答案。
通过例1,最重要的是理解lambda的本质是一个仿函数,例1的lambda表达式等价于下面的代码:
[id]
中没有任何特殊符号,表示默认以传值方式捕捉变量,也就相当于拷贝一份变量id
的副本到这个匿名函数中,且捕捉后不再受外界变量id的影响mutable
表示可以修改捕捉的变量,那么也就可以执行自增语句- 例1按照语句的执行顺序,在执行lambda表达式时,变量
id
的值为0时就已经被捕捉了,即使它被更新为42,后续调用lambda表达式时变量id仍然从0开始
但是,即使lambda表达式内部对变量id自增,最终变量id仍然为最后一次修改的值:42。
示例2
int main()
{int id = 0;auto f = [&id] (int param){cout << "id:" << id << ", ";cout << "param:" << param << endl;++id;++param;};id = 42;f(7);f(7);f(7);cout << "id:" << id << endl;return 0;
}
输出:
id:42, param:7
id:43, param:7
id:44, param:7
id:45
例2和例1的区别:去掉了mutable
,以传引用方式捕捉变量,新增参数param。
[&id]
,表示以引用方式捕捉变量id。那么匿名函数内部和外界处理的变量id
就是同一个,函数内部和外部都会互相影响。因此在调用lambda表达式之前变量id
被更新为42仍然会影响lambda表达式内部。- 参数param是值传递,每次传值都是一次临时拷贝。
示例3
int main()
{int id = 0;auto f = [id] (){cout << "id:" << id << ", ";++id; // 编译错误};id = 42;f();f();f();cout << "id:" << id << endl;return 0;
}
在自增语句编译错误:
error: cannot assign to a variable captured by copy in a non-mutable lambda
提示不能赋值给一个非可变的(non-mutable)变量。结合例1,言外之意是mutable
的修饰使得lambda表达式能够在内部修改捕捉到的变量。
示例4
int main()
{int id = 0;auto f = [id] () mutable{cout << "id:" << id << endl;++id;static int x = 5;int y = 6;return id;};f();return 0;
}
此例仅为了说明lambda表达式本质是一个匿名函数(底层由仿函数实现),所有函数的内部都能定义不同类型的变量,也允许有返回值。
4. 捕捉方式
基本方式
Lambda表达式的捕捉方式是指它如何访问外部变量,也就是定义在lambda表达式之外的变量。Lambda表达式的最基本的两种捕获方式是:按值捕获(Capture by Value)和按引用捕获(Capture by Reference)。
-
按值捕获是指lambda表达式内部保存一份外部变量的副本,对这个副本进行操作不会影响原来的变量。
-
按引用捕获是指lambda表达式内部直接使用外部变量的引用,对这个引用进行操作会影响原来的变量。
隐式和混合
除此之外,还有两种方式:隐式捕获和混合方式。它们是两种更简便的捕获方式,它们可以让编译器自动推断需要捕获的外部变量。
隐式捕获:
- [=]表示以值捕获的方式捕获所有外部变量(默认),成员函数包括this指针;
- [&]表示以引用捕获的方式捕获所有外部变量,成员函数包括this指针。
混合方式是指在隐式捕获的基础上,显式地指定某些变量的不同捕获方式。
混合使用时,要求捕获列表中第一个元素必须是隐式捕获(&或=),然后后面可以跟上显式指定的变量名和符号。
混合使用时,若隐式捕获采用引用捕获&,则显式指定的变量必须采用值捕获的方式;若隐式捕获采用值捕获=,则显式指定的变量必须采用引用捕获的方式,即变量名前加&。
一个例子:
int main()
{int a = 123;int b = 456;auto f = [=, &b]() { // 隐式值捕获a,显式引用捕获bcout << a << endl; // 输出:123cout << b << endl; // 输出:456b = 789; // 修改b的值};f();cout << b << endl; // 输出:789return 0;
}
输出:
123
456
789
补充
-
[=]以值捕获,是默认状态一般不写。
-
不允许以相同方式重复捕捉,例如[=, a],前者已经表示以值捕获所有外部变量,后者就重复了。但是[=, &a]是被允许的,因为它们的捕获方式不同。
-
Lambda表达式的父作用域是指定义lambda表达式的那个作用域,通常是一个函数或者一个类。Lambda表达式的块作用域是指lambda表达式本身的作用域,它是一个匿名函数,可以在其他地方调用。
-
Lambda表达式可以捕获父作用域中的局部变量,但是不能捕获其他作用域或者非局部变量。Lambda表达式捕获父作用域中的局部变量时,要求这个变量必须是声明为final的,或者实际上是final的(即不会被修改)。Lambda表达式不能在自己的块作用域中声明和父作用域中同名的参数或者局部变量。
-
Lambda表达式之间不能相互赋值,因为它们的本质是函数对象。即使从文本层面看它们的名字相同,但是底层编译器给它们取的名字是不同的。
5. 传递lambda表达式
C++传递lambda表达式的方法有以下几种:
- 将lambda表达式转换为相应的函数指针(如果没有捕获任何变量)。使用std::function作为参数类型,它可以接受任何可调用对象(包括捕获变量的lambda表达式)。例如:
void foo(std::function<bool(int)> f)
{// 函数动作
}foo([] (int x) // 传递lambda表达式{ return x > 0; }
);
- 将lambda表达式赋值给一个变量,然后将这个变量传递给函数。例如:
auto add = [] (int a, int b) // 将lambda表达式赋值给add变量
{ return a + b;
}; int result = add(1, 2); // 调用lambda表达式
bar(add); // 传递lambda表达式给变量bar
- 使用decltype来推导出lambda表达式的类型,然后将这个类型作为模板参数传递给函数。例如:
template<typename F>
void baz(F f)
{// 函数动作
}auto mul = [] (int a, int b)
{return a * b;
}; // 定义lambda表达式baz<decltype(mul)>(mul); // 传递lambda表达式和它的类型给变量baz
示例
下面以第三种方式为例:
int main()
{auto cmp = [](int a, int b){return a > b;};set<int, decltype(cmp)> s(cmp);return 0;
}
这段代码中使用了decltype是为了推断出lambda表达式的类型。decltype是一个C++11引入的关键字,它可以根据表达式的类型返回一个类型。在这个例子中,decltype(cmp)就是lambda表达式的类型,它被用作set容器的第二个模板参数,指定了set中元素的比较方式。set的第三个模板参数需要一个比较函数类型,而lambda表达式本身没有一个固定的类型,decltype可以根据lambda表达式的operator()来推导出它的函数类型,从而满足set的要求。
如果不使用decltype,就需要显式地写出lambda表达式的类型,但这很麻烦,因为lambda表达式的类型是编译器生成的,并没有一个具体的名字。你就需要自己定义一个比较类或者函数,并将其作为模板参数传递给std::set。
在这里暂不讨论泛型lambda表达式。
6. 原理
在上文已经提到,lambda表达式的本质就是一个函数对象,即仿函数,下面分别是两个功能相同的lambda表达式和函数对象:
class Add
{
public:Add(int base):_base(base){}int operator()(int num){return _base + num;}
private:int _base;
};
int main()
{int base = 1;// 函数对象Add add1(base);add1(1);// lambda表达式auto add2 = [base](int num){return base + num;};add2(1);return 0;
}
网站链接:https://godbolt.org/
通过汇编代码,可以看到编译器处理lambda表达式的方式和处理函数对象相同。原因是C++的lambda表达式底层实现是一个类,它重载了operator()
运算符,使得它可以像函数一样被调用。这个类还包含了lambda表达式捕获的变量,它们可以是值捕获或引用捕获。
一个lambda表达式产生一个临时对象,它可以用来初始化一个栈上的变量。这个对象有构造函数和析构函数,并且遵循所有C++规则。
这就是lambda表达式即使在上层看来名字相同也不能相互赋值的原因,编译器在底层给它们各自的函数对象的类起了不同的名字。例如在VS编译器中,具体类名为:lambda_uuid
。
类名的 uuid (Universally Unique Identifier)是一个用于标识类的唯一标识符。
lambda_uuid
即lambda表达式的类型。所以lambda表达式的“匿名”属性是对于使用者而言的,对于编译器来说是有名的。
7. 内联属性
C++的lambda表达式是一种方便的定义匿名函数对象(Go语言中的闭包)的方法,它可以在调用或作为参数传递给函数的地方直接定义。通常lambda表达式用于封装一些传递给算法或异步函数的代码。
C++11中引入了λ表达式,它可以用来定义一个**内联 (inline)**的函数,作为一个本地的对象或者一个参数。内联函数是一种编译器优化技术,它可以避免函数调用的开销,提高执行效率。λ表达式可以被编译器自动内联展开,从而减少函数调用的次数。但是,并不是所有的λ表达式都会被内联,这取决于编译器的实现和优化策略。
一般来说,lambda表达式会被内联的条件是:
- lambda表达式作为参数传递给一个内联函数
- lambda表达式没有被保存或者传递到其他地方
- lambda表达式没有从更外层的函数返回
如果不满足这些条件,可以使用noinline关键字来标记lambda参数,表示不要求内联。另外,编译器也会根据实现和优化策略来决定是否内联lambda表达式。
如果一个函数被调用的频率很低,甚至只有一次,那么内联属性使得它在被调用的位置展开,能减少调用函数创建栈帧的开销。lambda表达式的内联属性是默认的,只要编译器认为它可以在某处展开,那么它就像一个内嵌语句一样。
相关文章:

C++11:lambda表达式
文章目录1. 概念2. 语法3. 示例示例1示例2示例3示例44. 捕捉方式基本方式隐式和混合补充5. 传递lambda表达式示例6. 原理7. 内联属性1. 概念 lambda表达式实际上是一个匿名类的成员函数,该类由编译器为lambda创建,该函数被隐式地定义为内联。因此&#…...

【Android -- 开源库】表格 SmartTable 的基本使用
介绍 1. 功能 快速配置自动生成表格;自动计算表格宽高;表格列标题组合;表格固定左序列、顶部序列、第一行、列标题、统计行;自动统计,排序(自定义统计规则);表格图文、序列号、列标…...

自动化测试实战篇(9),jmeter常用断言方法,一文搞懂9种测试字段与JSON断言
Jmeter常用的断言主要有,JSON断言和响应断言这两种方式。 断言主要就是帮助帮助人工进行快速接口信息验证避免繁杂的重复的人工去验证数据 第一种响应断言Apply to:表示应用范围测试字段:针对响应数据进行不同的匹配响应文本响应代码响应信息…...

vue-virtual-scroll-list虚拟列表
当DOM中渲染的列表数据过多时,页面会非常卡顿,非常占用浏览器内存。可以使用虚拟列表来解决这个问题,即使有成百上千条数据,页面DOM元素始终控制在指定数量。 一、参考文档 https://www.npmjs.com/package/vue-virtual-scroll-li…...

C++学习笔记(以供复习查阅)
视频链接 代码讲义 提取密码: 62bb 文章目录1、C基础1.1 C初识(1) 第一个C程序(2)注释(3)变量(4)常量(5)关键字(6)标识符命名规则1.2 …...

备份时间缩短为原来 1/4,西安交大云数据中心的软件定义存储实践
XEDP 统一数据平台为西安交通大学云平台业务提供可靠的备份空间和强大的容灾能力,同时确保数据安全。西安交通大学(简称“西安交大”)是我国最早兴办、享誉海内外的著名高等学府,是教育部直属重点大学。学校现有兴庆、雁塔、曲江和…...

我国近视眼的人数已经超过了六亿,国老花眼人数超过三亿人
眼镜是一种用于矫正视力问题、改善视力、减轻眼睛疲劳的光学器件,在我们的生活中不可忽略的一部分,那么我国眼镜市场发展情况是怎样了?下面小编通过可视化互动平台对我国眼镜市场的状况进行分析。我国是一个近视眼高发的国家,据统…...

设计模式(十八)----行为型模式之策略模式
1、概述 先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。 作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发&a…...

VUE3入门基础:input元素的type属性值说明
说明 在Vue 3中,<input>元素的type属性可以设置不同的类型,以适应不同的输入需求。 常见的type属性取值如下: text:默认值,用于输入文本。password:用于输入密码,输入内容会被隐藏。em…...

关于供应链,一文教你全面了解什么是供应链
什么是供应链?供应链是指产品生产和流通过程中所涉及的原材料供应商、生产商、分销商、零售商以及最终消费者等成员通过与上游、下游成员的连接 (linkage) 组成的网络结构。也即是由物料获取、物料加工、并将成品送到用户手中这一过程所涉及的企业和企业部门组成的一…...

Scope作用域简单记录分析
类型 singleton 单例作用域 prototype 原型作用域 request web作用域,请求作用域,生命周期跟request相同,请求开始bean被创建,请求结束bean被销毁 session web作用域,会话作用域,会话开始bean被创建,会话结束bean被销毁 application web作用域,应用程序作用域,应用程序创建…...

ChatGPT创作恋爱甜文
林欣是一个长相可爱、性格呆萌的小姑娘,她年纪轻轻就失去了父母,独自一人面对世界的冷漠和残酷。 虽然经历了这样的打击,但她并没有沉沦,反而更加努力地去生活。 她找到了一份服务员的工作,每天在餐厅里穿梭…...

贝叶斯优化及其python实现
贝叶斯优化是机器学习中一种常用的优化技术,其目的是在有限步数内寻找函数的最大值或最小值。它可以被视为在探索不同参数配置与观察这些配置结果之间寻求平衡点的过程。基本思想是将我们在过去的观察和体验,传递到下一个尝试中,从而在等待数…...

Lombok使用@Builder无法build父类属性
文章目录问题描述解决方案使用示例lombok Builder注解和build父类属性问题1、简介2.使用3、Builder注解对类做了什么?问题描述 实体类使用Lombok的Builder来实现Builder模式,但是如果使用了extend继承,则子类无法通过Builder来Build父类属性…...

Pixhawk RPi CM4 Baseboard 树莓派CM4安装Ubuntu20.04 server 配置ros mavros mavsdk
文章目录硬件安装Ubuntu Server20.04下载rpiboot工具下载imager刷写系统配置USB配置WIFI开机安装桌面配置wifi配置串口安装ROS安装mavros安装MAVSDK-PythonInternet设置最后参考: https://docs.holybro.com/autopilot/pixhawk-baseboards/pixhawk-rpi-cm4-baseboard…...

后端开发过程中的安全问题
安全问题是木桶效应,整个系统的安全等级取决于安全性最薄弱的那个模块。在写业务代码的时候,要从我做起,建立最基本的安全意识,从源头杜绝低级安全问题。 1、数据源头的安全处理 对于 HTTP 请求,我们要在脑子里有一个…...

基于Hyperledger Fabric的学位学历认证管理系统
基于Hyperledger Fabric的学位学历认证管理系统 项目源码:https://github.com/Pistachiout/Academic-Degree-BlockChain 一、选题背景 学历造假、认证造假等是一个全球日益普遍的现象,不仅对社会产生了巨大的负面影响,同时也极大增加了企业…...

jq条件判断验证,正则表达式
// 判断是否包含为至少8位及以上字符,大小写字母及特殊字符 jQuery.validator.addMethod("isPwd", function (value, element) { var pwdRegex new RegExp((?.*[0-9])(?.*[A-Z])(?.*[a-z])(?.*[^a-zA-Z0-9]).{8,30}); return this.optional(elemen…...

23.3.9打卡 AtCoder Beginner Contest 259
A题 题解 对于x特判一下就好 代码 void solve() {ll x,d;cin>>n>>m>>x>>t>>d;if(n>m){nmin(n,x);if(n<m){cout<<t;return;}cout<<(m-n)*dt;}else{mmin(m,x);cout<<(m-n)*dt;}return; }B 三角函数全还给高中老师了 题…...

JS - this指向
一 this 指向有哪几种 详细可见:https://juejin.cn/post/6844903805587619854 (文章归类this指向为四大类) https://www.jianshu.com/p/66eb9b21105d this是什么?this 就是一个指针,指向调用函数的对象。 1.默认绑定…...

低代码有哪些典型应用场景?
低代码有哪些典型应用场景? 低代码是一种全新的应用开发方式,它通过可视化的拖拽式界面,将传统的繁琐代码编写转化为简单的拖拽操作,让非技术人员也能够快速地开发出应用程序。 随着数字化转型的不断加速,低代码平台…...

Substrate 基础教程(Tutorials) -- 监控节点指标
Substrate 公开有关网络操作的度量。例如,您可以收集有关您的节点连接了多少个对等节点、您的节点使用了多少内存以及正在生成的块数量的信息。为了捕获和可视化Substrate节点公开的度量,您可以配置和使用Prometheus和Grafana等工具。本教程演示如何使用…...

lua table 详解
文章目录1.table 声明与访问2. table 的两种遍历方式 pairs 和 ipairs2.1 pairs2.2 ipairs2.3 例3.迭代器实现 ipairs 效果4.获取 table 长度4.1 #table 获取长度4.2 自定义函数获取 table 长度注意事项1.table 声明与访问 -- 初始化表 tbl {} -- 不可以是 tbl nil-- 赋值 -…...

Element表单嵌套树形表格的校验问题
普通嵌套 表单和表格是项目中最常见的项目,有时候就会遇到表格嵌套表单的情况,比如 这种形式的其实挺好处理的,如下: <el-form:model"states"ref"tableFormRef"label-width"80px"label-posit…...

1.webpack的基本使用
webpack是做工程化用的,并且可以对代码进行压缩(搞成min.js那样),处理浏览器端JS兼容性,性能优化 vue-cli与webpack作用相同 目录 1 基本使用 1.1 引入场景 1.2 安装webpack 1.3 配置webpack 1.4 使用webpack 2 webpack.config.…...

面试必看:谈谈你所了解的JVM调优,JVM性能调优总结
文章目录从面试角度来谈谈你了解的JVM调优GC调优的步骤1.确定目标:2.优化参数3.验收优化结果GC优化案例一、Major GC和Minor GC频繁优化Minor GC频繁问题:1.可以适当增大新生代的内存二、请求高峰期发生GC,导致服务可用性降低优化标记停顿时间…...

Pytorch优化器Optimizer
优化器Optimizer 什么是优化器 pytorch的优化器:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签 导数:函数在指定坐标轴上的变化率 方向导数:指定方向上的变化率(二元及以上函数,偏导数&am…...

如何在MySQL 8中实现数据迁移?这里有一个简单易用的方案
文章目录前言一. 致敬IT领域的那些女性二. 进制方式安装MySQL2.1 下载软件包2.2 配置环境:2.2.1 配置yum环境2.2.2 配置安全前的系统环境2.3 开始安装2.4 初始化MySQL2.5 修改配置文件2.6 将MySQL设为服务并启动测试三. MySQL数据迁移总结前言 正好赶上IT女神节&am…...

java多线程(二三)并发编程:Callable、Future和FutureTask
一、Callable 与 Runnable 先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法: public interface Runnable {public abstract void run(); }由于run()方法返回值为void类型,所以在执行完任务之后无法返…...

day4分支和循环作业
基础题 根据输入的成绩的范围打印及格 或者不及格。 score 58 if score > 90:print(及格) else:print(不及格)根据输入的年纪范围打印成年或者未成年,如果年龄不在正常范围内(0~150)打印这不是人!。 age 52 if 0 < age < 18:print(未成年) elif 18 &l…...