c++中的lambda表达式

简介 & 用法

lambda表达式是c++11引入的一个重要特性,基本语法如下

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

其中捕获列表和形参列表可以为空,返回值类型大部分情况下可以忽略不写。
lambda表达式的结构整体上和普通函数一样,特殊在比普通函数多一个捕获列表。捕获列表的作用是获取作用域以外的局部变量给函数体使用。捕获器有两种形式:

  • 按值捕获:语法[localValue],原理和形参列表的按值传递类似。
  • 按引用捕获:语法[&localValue],原理和形参列表的按引用传递类似。

举个例子

int main {int base = 100;int count = 0;auto trans = [base, &count](int value) {++count;return base + value;};int res = trans(5);printf("base=%d, count=%d, res=%d\n", base, count, res);
}

示例代码中的lambda表达式trans按值捕获局部变量base、按引用捕获局部变量count,因此lambda表达式内部修改base不会影响外部base的值,而修改count则会影响外部count的值。

lambda表达式的原理

本质:lambda本质上是函数对象(函数对象的介绍见本文最后一节 扩展知识)。
为了弄清楚lambda表达式的原理,我们需要研究研究示例代码的汇编码

main::{lambda(int)#1}::operator()(int) const:push    rbp...ret
main:push    rbp...call    main::{lambda(int)#1}::operator()(int) const...ret

函数调用语句int res = trans(5);对应的汇编码为call指令,调用的函数符号main::{lambda(int)#1}::operator()(int)代表的是匿名类main::{lambda(int)#1}的函数调用操作符operator()
函数操作符operator()?这不是函数对象嘛!为了一探究竟,我们用函数对象重写示例代码对比两者的汇编码看看

int main() {int base = 100;int count = 0;// auto trans = [base, &count](int value) {//     ++count;//     return base + value;// };struct TransLambda {TransLambda(int& count) : _count(count) { }int operator()(int value) {++_count;return _base + value;}int& _count;int _base;};TransLambda trans(count);int res = trans(5);
}

这段代码的汇编码如下

main::TransLambda::TransLambda(int&) [base object constructor]:push    rbp...ret
main::TransLambda::operator()(int):push    rbp...ret
main:push    rbp...call    main::TransLambda::TransLambda(int&) [complete object constructor]...call    main::TransLambda::operator()(int)...leaveret

对比lambda表达式版本和函数对象两个版本的汇编码,发现两段汇编码的内容几乎一样。破案了,lambda表达式就是函数对象,也可以认为lambda是函数对象的语法糖。
为了进一步理解,我们不妨大胆猜测一下编译器拿到lambda函数后做了几件事

  • 定义匿名类main::{lambda(int)#1}
  • 参考lambda表达式的内容,重载main::{lambda(int)#1}的函数调用操作符operator()
  • 为匿名类main::{lambda(int)#1}分配一个实例,并赋值给变量trans
  • 调用实例trans的函数调用操作符operator()

所以lambda表达式实际上是语法更为简洁的函数对象,它的特殊部分捕获列表实际上是传给函数对象的成员变量

  • 按值捕获:相当于按值传参,函数对象持有的是这个变量的拷贝。
  • 按引用捕获:相当于按引用传参,函数对象持有的是这个变量的引用。
  • 捕获列表和形参列表的区别在于 捕获列表捕获到的变量由函数对象的成员变量维护,生命周期于函数对象一样;而形参列表的入参生命周期是函数级别的。

避坑指南

static修饰的lambda表达式不能按引用捕获局部变量。

例如

std::string GetString() {std::string id = "Test";static auto appendId = [&id](std::string value) {return name + "-" + value;};std::string res = appendId("Hello");
}int main() {std::string res1 = GetString();std::string res2 = GetString();    // 可能引发奔溃
}

原因很简单,lambda表达式appendId按引用捕获局部变量id,所以appendId会持有id的引用,但是因为appendIdstatic修饰的所以生命周期比局部变量id长,当id销毁以后appendId持有的id引用实际上已经销毁了,出现未定义的行为。

(其他坑待补充…)

扩展知识

函数对象(仿函数)

函数对象,也叫仿函数。如果一个类重载了函数调用操作符operator(),这个类的实例就叫函数对象,可以像使用函数一样使用这个对象。例如

Struct Transform {int operator()(int value) {++count;return 100 + value;}int count = 0;
};int main() {Transform trans;int res = trans(5);
}

Transform的实例trans就是一个函数对象,我们可以像调用函数一样使用trans。函数对象的特殊之处在于可以保持状态,例如trans可以通过成员变量count统计自己被调用过多少次,而普通函数做不到这一点。

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

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

相关文章

docker study

一些基本命令 查看构建的镜像列表: 使用以下命令查看已经构建的 Docker 镜像: docker images这将显示你本地计算机上的所有 Docker 镜像,找到你刚刚构建的镜像并记下它的名称和标签。 运行 Docker 容器: 使用以下命令运行 Docker…

力扣题库第6题:三数之和

题目内容: 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重…

axios的详细使用

目录 axios:现代前端开发的HTTP客户端王者 一、axios简介 二、axios的基本用法 1. 安装axios 2. 发起GET请求 3. 发起POST请求 三、axios的高级特性 1. 拦截器 2. 取消请求 3. 自动转换JSON数据 四、axios在前端开发中的应用 五、总结 axios&#xff1a…

【JS】判断是否安装了某个Chrome插件

前提 manifest.json 清单 下文均以manifest.json v3介绍。 因为Chrome官方文档中明确说明,v2已经弃用了。 ID 由于浏览器的安全策略,以下方法均在「已知扩展程序 ID」 的前提下才可实现。 获取扩展程序ID 进入扩展程序管理页,找到对应插…

Python基本数据类型之散列类型详解

前言: python的基本数据类型可以分为三类:数值类型、序列类型、散列类型,本文主要介绍散列类型。 一、散列类型 散列类型:内部元素无序,不能通过下标取值 1)字典(dict)&#xff…

vscode中使用nvm安装node及创建vue3项目

使用vscode创建vue3项目 1。安装nvm Releases coreybutler/nvm-windows (github.com) 打开下载nvm.exe并安装 2。安装node.js 用管理员身份打开vscode,新建终端选择git bash,运行nvm list available选择lts版本,比如:16.16.…

【DIY】电子制作创意作品:有趣的激光竖琴

在上海世博会的伊朗馆,我看到了一架没有琴弦的竖琴,那是众多参观者公认的伊朗馆里最有趣的展品!参观者只要伸手穿过那架通体黑色的竖琴,音调就会被“奏响”。没有琴弦怎么奏响?工作人员为我们揭示了秘密——他按了一下…

Spring Boot搭建入门

Spring Boot简介 Spring Boot是对Spring进行的高度封装,是对Spring应用开发的高度简化版,是Spring技术栈的综合整合,是J2EE的一站式解决方案。想要精通Spring Boot的前提是需要熟悉Spring整套技术栈原理与内容。 Spring Boot的优点&#xf…

背景虚拟化组件,透明模糊

问题当我们背景想要进行透明或者模糊处理的时候我们一般我们可以以通过 rgba 的第四个位置可以进行透明处理,但是模糊不行 需要懂得知识点,定位,属性加强,结构化,react 插槽 话不多说上代码 子组件 import logincs…

RN的父传子和子传父及方法调用(函数式组件)

在React Native中,父组件向子组件传递数据通常通过props实现,而子组件向父组件传递数据则通常通过回调函数实现。下面是一个简单的示例,演示了父组件向子组件传递数据和子组件向父组件传递数据的方法: 父传子 父组件 // ParentC…

指针篇章-(冒泡排序详解)

冒泡排序 图解 tmp图解 内容图解 每次循环的次数减少 for循环详解 冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列, 一次比较两个元素,如果它们的顺序错误就把它们交换过来。 遍历数列的工作是重复地进行直到没有再需要交换&…

Double和Float类

Double类 功能:实现对Double基本型数据的类包 构造方法: (double num) double Value()方法:返回对象中的double型数据。 Float类 功能:实现对float基本型数据的类包装。 构造方法: (float num) Float Value()方法…

云计算项目九:K8S安装

K8S安装 Kube-master安装 按照如下配置准备云主机 防火墙相关配置:禁用selinux,禁用swap,且在firewalld-*。上传kubernetes.zip 到跳板机 配置yum仓库(跳板机) 跳板机主机配置k8s软件源服务端 [rootjs ~]# yum -y…

设计模式-行为型模式-备忘录模式

备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。[DP] //首先,我们定义Originator类,它有一个状态和…

C++初阶:类与对象(中篇)

目录 2. 类的六个默认成员函数2.1 构造函数2.1.1 构造函数的定义方式 2.2 析构函数2.2.1 析构函数定义方式 2.3拷贝构造函数2.3.1 拷贝构造函数的定义方式2.3.2 深拷贝与浅拷贝 2.4 赋值运算符的重载2.4.1 运算符重载2.4.2 运算符的重载的定义方式2.4.3 默认成员函数&#xff1…

HTML使用

文章目录 一、简介二、HTML快速入门三、基础标签四、图片、音频、视频标签五、超链接标签六、列表标签七、表格标签八、布局标签九、表单标签十、表单向标签 一、简介 二、HTML快速入门 ​ <html><head><title>你好</title></head><body>再…

功能强大使用简单的截图/贴图工具,PixPin

一、下载链接 PixPin 截图/贴图/长截图/文字识别/标注 | PixPin 截图/贴图/长截图/文字识别/标注 (pixpinapp.com) 二、功能 截图/贴图/长截图/文字识别/标注 三、安装教程 根据提示安装即可&#xff1a; 四、快捷键 1.软件自带快捷键&#xff08;右击PixPin查看 &#xff09…

spring 技术100问?

什么是Spring框架的主要优点&#xff1f;Spring框架支持哪些模块或组件&#xff1f;什么是Spring Core模块&#xff1f;请解释Spring框架中的BeanFactory和ApplicationContext的区别。如何自定义Spring Bean的初始化和销毁方法&#xff1f;什么是Spring的工厂方法设计模式应用&…

微信小程序-入门

1.下载和安装Npm&#xff1a;Npm https://docs.npmjs.com/downloading-and-installing-node-js-and-npm 或者 https://nodejs.org/en/download/ 未安装npm 提示 以下以安装node安装包为例 按任意键继续 安装完成后 2. 下载和安装小程序开发工具 &#xff1a;https:/…

安信可IDE(AiThinker_IDE)编译ESP8266工程方法

0 工具准备 AiThinker_IDE.exe ESP8266工程源码 1 安信可IDE&#xff08;AiThinker_IDE&#xff09;编译ESP8266工程方法 1.1 解压ESP8266工程文件夹 我们这里使用的是NON-OS_SDK&#xff0c;将NON-OS_SDK中的1_UART文件夹解压到工作目录即可 我这里解压到了桌面&#xff0c…