Lambda表达式从用到底层原理

news2024/11/18 10:24:24

文章目录

  • 前言
  • 一、lambda函数基本使用
      • 参数列表
      • 返回类型
      • 函数体
      • 捕获列表
            • 值捕获
            • 引用捕获
            • 隐式捕获
            • 混合方式捕获
            • 修改值捕获变量的值
            • 异常说明
  • 二、lambda表达式使用的注意事项
      • 避免默认捕获模式
  • 三、lambda表达式底层实现原理
      • 采用值捕获
      • 采用引用捕获


前言

lambda式作为一种创建函数对象的手段,实在太过方便,对c++日常软件开发产生极大影响,所以特来学习。


一、lambda函数基本使用

lambda函数是C++11标准新增的语法糖,也称为lambda表达式或匿名函数。
lambda函数的特点是:距离近、简洁、高效和功能强大。
示例:

[](const int& no) -> void { cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n"; };

语法:
在这里插入图片描述

参数列表

参数列表是可选的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写。
与普通函数的不同:

  • lambda函数不能有默认参数。
  • 所有参数必须有参数名。
  • 不支持可变参数。

返回类型

用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。
如果有返回类型,建议显式的指定,自动推断可能与预期不一致。

auto f=[](const int& no) ->double{
	cout << "亲爱的" << no << "号:我是一只傻傻鸟。\n";
};

此时auto判定f为double类型;

函数体

类似于普通函数的函数体。

捕获列表

通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。
捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。
以下列出了不同的捕获列表的方式。
在这里插入图片描述

值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。
与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。
例如:

size_t v1 = 42;
auto f = [ v1 ]  { return v1; };	// 使用了值捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f();    // j为42,f保存了我们创建它是v1的拷贝。

由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值
默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。

引用捕获

和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。

size_t v1 = 42;
auto f = [ &v1 ]  { return v1; };	 // 引用捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f();	   // j为0。

如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的

隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

int a = 123;
auto f = [ = ]  { cout << a << endl; };		//值捕获
f(); 	// 输出:123
auto f1 = [ & ] { cout << a++ << endl; }; 		//引用捕获
f1();	//输出:123(采用了后++)
cout << a << endl; 		//输出 124
混合方式捕获

lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。
需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:

	int i = 10;
	int  j = 20;
	auto f1 = [ =, &i] () { return j + i; };		// 正确,默认值捕获,显式是引用捕获
	auto f2 = [ =, i] () { return i + j; };		// 编译出错,默认值捕获,显式值捕获,冲突了
	auto f3 = [ &, &i] () { return i +j; };		// 编译出错,默认引用捕获,显式引用捕获,冲突了
修改值捕获变量的值

在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。
在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。

    int a = 123;
    auto f = [a]()mutable { cout << ++a << endl; }; // 不会报错
    cout << a << endl; 	// 输出:123
    f(); 					// 输出:124
    cout << a << endl; 	// 输出:123
异常说明

lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。

二、lambda表达式使用的注意事项

避免默认捕获模式

按引用的默认捕获模式可能导致空悬引用,一旦由lambda式所创建的闭包越过了局部变量或形参的生命周期,那么闭包内的引用就会空悬(即必须保证被引用的对象在lambda执行的时候是存在的
(有没有空悬引用其实就是看的生命周期,那个长)

既然引用有导致空悬引用的风险,那是不是可以用按值捕获呢。按值的默认捕获也有可能存在空悬的风险。如按值捕获了一个指针以后,在lambda式创建的闭包中持有的是这个指针的副本,但并无办法阻止lambda式之外的代码去针对该指针实施delete操作所导致的指针副本空悬。

对于类的方法中使用lambda,如果使用到了类的成员变量,则会出现无法被捕获的错误。如下:

void Widget::addFilter() const
{
	filters.emplace_back(
		[divisor](int value)		//错误
		{ return value % divisor==0;}	//局部没有可捕获的divisor(divisor既不是局部变量,也不是形参)
	);
}

解决这一问题,关键在于一个裸指针隐式应用,这就是this。每一个非静态成员函数都持有一个this指针,然后每当提及该类的成员变量时都会用到这个指针。
所以此上的代码的lambda函数被捕获的实际上是Widget的this指针,而不是divisor。
代码如下 :

void Widget::addFilter() const
{
	auto currentObjectPtr=this;
	filters.emplace_back(
		[currentObjectPtr](int value)
		{return value%currentObjectPtr->divisor==0;}
	);
}

这就相当于lambda闭包的存活与它含有其this指针副本的Widget对象的生命期是绑在一起的

对于以static声明的静态变量,可以在lambda内使用,但是它们不能被捕获

三、lambda表达式底层实现原理

class Add{
public:
	Add(int n):_a(n){
	}

	int operator()(int n){
		return _a + n;
	}
private:
	int _a;
};

int main(){
	int n = 2;
	Add a(n);
	a(4);

	auto a2 = [=](int m)->int{return n + m; };
	a2(4);
	return 0;
} 

从上面的代码中可以看到,仿函数与lambda表达式完全一样
在这里插入图片描述
实际当我们编写了一个lambda表达式之后,编译器将该表达式翻译成一个未命名类的未命名对象。该类含有一个operator()。
整个lamda表达式,编译的时候,

  1. 编译器给你自动生成一个形如 <lambda_b328511335c7e943aa98460a349659c7> 的类
  2. 然后把捕获列表中的参数,都按照你的要求(值捕获, 引用捕获)包装到这个类的成员里面
  3. 编译器生成一个 operator() 重载函数, 最后你对lamda的调用就是对函数对象的调用了, 捕获的参数早给你准备好了

采用值捕获

采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。
例如:

int a =10;
int b = 20;
auto addfun = [=] (const int c ) -> int { return a+c; };
int c = addfun(b);    
cout << c << endl;

等同于:

class Myclass
{
	int m_a;		// 该成员变量对应通过值捕获的变量。
public:
	Myclass( int a ) : m_a(a){};	// 该形参对应捕获的变量。
	// 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。
	int operator()(const int c) const
	{
		return a + c;
	}
};

默认情况下,由lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。

采用引用捕获

如果lambda函数采用引用捕获的方式,编译器直接引用就行了。
唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python tkinter -- 第18章 画布控件之多边形

18.2.19 create_polygon(coords, **options) 根据 coords 给定的坐标&#xff0c;在画布上绘制一个多边形。 &#xff08;1&#xff09;coords&#xff1a;给定多边形的坐标 &#xff08;2&#xff09;options&#xff1a;选项的具体含义&#xff1a; 选项含义activedash当鼠标…

JavaEE- JVM八股文(JVM垃圾回收机制GC)

JVM垃圾回收的目标&#xff1a;主要针对内存中的堆空间进行垃圾回收。 Java中&#xff0c;大量的内存都在堆中。 程序计数器&#xff1a;固定大小&#xff0c;不涉及释放 栈&#xff1a;函数执行完毕&#xff0c;对应栈的空间就自动释放了&#xff0c;不需要垃圾回收 方法区&…

07-Golang中标识符的命名规则

Golang中标识符的命名规则标识符概念标识符的命名规则保留关键字介绍预定义标识符介绍标识符命名注意事项标识符概念 1.Golang对各种变量、方法等命名时使用的字符序列称为标识符 2.凡是自己可以起名字的地方都叫标识符 标识符的命名规则 1.由26个英文字母大小写&#xff0…

华为云桌面之下的“冰山”:技术底座x繁荣生态加速模式进化

在新兴技术迭代升级持续加速的背景下&#xff0c;很多产品类别的内涵和外延都在不断演进——虽然名字没什么变化&#xff0c;但实际所指已有云泥之别。 “云桌面”即是如此。从早期的无盘工作站&#xff0c;到VDI、IDV和VOI等技术流派的群雄并起&#xff0c;云桌面的江湖总是“…

linux第七章---管道、环境变量、常用命令

1.管道 1.1概念&#xff1a; 管道类似于文件重定向&#xff0c;可以将前一个命令的stdout重定向到下一个命令的stdin。 1.2要点&#xff1a; 管道命令仅处理stdout&#xff0c;会忽略stdeer。管道右边的命令必须要能接受stdin.多个管道命令可以串联。 1.3与文件重定向的区…

Java平衡树之查找树的详解(1)

1.平衡树 之前我们学习过二叉查找树&#xff0c;发现它的查询效率比单纯的链表和数组的查询效率要高很多&#xff0c;大部分情况下&#xff0c;确实是这样的&#xff0c;但不幸的是&#xff0c;在最坏情况下&#xff0c;二叉查找树的性能还是很糟糕。例如我们依次往二叉查找树中…

c语言预处理(万字解析)

预处理一.总体概述1.注释去除2.宏替换二.宏定义&#xff08;宏替换类型&#xff09;1.数值宏常量2.字符串宏常量3.用宏定义注释符号4.用宏定义表达式&#xff08;难点&#xff09;1.第一种情况2.第二种情况5.#undef&#xff08;宏的有效范围&#xff09;1.两个问题2.#undef的使…

FLStudio2023水果软件哪个版本好用?功能区别对比

FL Studio是一款功能非常强大的音乐创作编辑软件它就是FL Studio(水果软件)。使用FL Studio中文版可以轻松帮我们制作自己的音乐唱片&#xff0c;拥有强大且专业的编曲混音创作工具&#xff0c;有需要的朋友不要错过。 水果&#xff0c;全称Fruity Loop Studio&#xff0c;简称…

量子计算(十六):其他类型体系的量子计算体系

文章目录 其他类型体系的量子计算体系 一、离子阴量子计算 二、原子量子计算 三、核自旋量子计算 四、拓扑量子计算 其他类型体系的量子计算体系 一、离子阴量子计算 离子研量子计算在影响范围方面仅次于超导量子计算。早在2003年&#xff0c;基于离子阴就可以演示两比特…

“转行做程序员”很难?这里有4个重要建议

近几年来&#xff0c;传统行业多处于经济下行&#xff0c;加上互联网行业的赚钱效应&#xff0c;想要转行到这一行的人越来越多&#xff0c;其中程序员这个行业更是很多人梦寐以求的。 但另一方面&#xff0c;我们也发现&#xff0c;这些想要转行的同学们往往会遇到很多困扰。…

推荐今日 火火火火 的开源项目

本期推荐开源项目目录&#xff1a;1. coding-interview-university2. 前端后台管理模板3. 钉钉聊天机器人4. 基于 ChatGPT 的 Neovim 插件5. 开源的分布式社交网络平台6. 分析社交媒体内容7. 用于绘制流程图的库01coding-interview-university这是一个汇集了软件工程师面试中常…

2023跨年代码(烟花+背景音乐)

文章目录前言效果展示使用方法源码学习HTML代码CSS代码前言 时光荏苒&#xff0c;白驹过隙。2022这一年又在忙碌中度过了&#xff0c;过去的一年&#xff0c;我们同努力&#xff0c;我们共欢笑&#xff0c;每一次成功都蕴藏着我们辛勤的劳动。 新的一年即将来到&#xff0c;我…

【linuxgdb】动态链接和静态链接的区别,gdb的基础使用

目录 1.gcc/c动态链接和静态链接的区别 2.gdb的基础使用 2.2使用gdb调试 1.gcc/c动态链接和静态链接的区别 1.1查看可执行文件是动态还是静态的 格式&#xff1a;file文件名 1.2动态链接和静态链接 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的…

ARM S5PV210 串行通信接口详解

一、串行通信接口详解 1 0. 串口的名称 S5PV210 的数据手册中串口控制器在 section8.1串口的官方名称叫&#xff1a;universal asynchronous reciver and transmitter&#xff0c;通用异步收发器 英文缩写是uart&#xff0c;中文简称串口。 1. S5PV210 的串口控制器工作原理…

CSS -- 2D转换各属性讲解(translate,rotate,scale)

文章目录2D转换1 二维坐标系2 2D转换之移动 translate3 2D转换之旋转 rotate4 2D转换中心点 transform-origin5 2D转换之缩放scale6 2D转换综合写法7 2D转换总结2D转换 转换(transform)是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 转换(t…

[ 常用工具篇 ] 使用 kali 实现网络钓鱼 -- setoolkit 详解实战(一)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

密码学讲座PPT

来自山大密码学讲座的PPT。 一些密码学领域常用名词术语 Diffie-Hellman 密钥交换&#xff1b;Elgamal 加密和签名&#xff1b;DSA 签名&#xff1b;因相应的离散对数问题难解&#xff0c;大素数的原根可用于密钥交换;RSA加密和签名: 因大整数因子分解难算&#xff0c;合数可成…

JetPack 组件总结

文章目录JetPackLifecycle使用Lifecycle解耦页面和组件使用Lifecycle解耦Service与组件使用ProcessLifecycleOwner监听应用程序生命周期ViewModel 与 LiveDataViewModelLiveDataViewModel LiveData 实现Fragment间通信DataBinding 的意义与应用意义使用前的配置import标签事件…

SWPUCTF 2021 新生赛

&#x1f60b;大家好&#xff0c;我是YAy_17&#xff0c;是一枚爱好网安的小白&#xff0c;自学ing。 本人水平有限&#xff0c;欢迎各位大佬指点&#xff0c;一起学习&#x1f497;&#xff0c;一起进步⭐️。 ⭐️此后如竟没有炬火&#xff0c;我便是唯一的光。⭐️ 目录 g…

Kotlin + Jpa + Querydsl

Kotlin Jpa Querydsl 本篇主要介绍一下 kotlin jpa querydsl . jpa querydsl 是我很喜欢的一种搭配,它能够让你写sql语句一样的去写代码 , 以前我也写过关于 java版本的, 本篇就来看看kotlin中如何去使用 1.引入插件 主要引入jpa插件 和 kapt Kotlin annotation processi…