c++11新特性-lambda表达式

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表达式的语法:

#include <iostream>
using namespace std;int main()
{[] () {cout << "hello lambda" << endl;};return 0;
}

一般不会以这种方式写代码,还不如直接打印来得快,比较可能得一种使用形式:

#include <iostream>
using namespace std;int main()
{auto l = [] {cout << "hello lambda" << endl;};l();return 0;
}

注意:
1> 上面的例子和概念中的“内联”属性并没有关系。
2> lambda表达式在main函数之外定义也不影响结果。
3> lambda表达式的最后有分号。
4< lambda函数的参数列表和返回值类型不是必要的,但捕捉列表和函数体是不可省略的。习惯上不写返回值,让表达式自动推导。

3. 示例:

3.1 示例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表达式不用传参

3.2 示例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;
}

例2和例1的区别:去掉了mutable,以传引用方式捕捉变量,新增参数param。

[&id],表示以引用方式捕捉变量id。那么匿名函数内部和外界处理的变量id就是同一个,函数内部和外部都会互相影响。

3.3 示例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表达式能够在内部修改捕捉到的变量。

3.4 示例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. 捕捉方式


4.1 基本方式

Lambda表达式的捕捉方式是指它如何访问外部变量,也就是定义在lambda表达式之外的变量。Lambda表达式的最基本的两种捕获方式是:按值捕获(Capture by Value)和按引用捕获(Capture by Reference)。
1> 按值捕获是指lambda表达式内部保存一份外部变量的副本,对这个副本进行操作不会影响原来的变量。
2> 按引用捕获是指lambda表达式内部直接使用外部变量的引用,对这个引用进行操作会影响原来的变量。

4.2 隐式和混合

除此之外,还有两种方式:隐式捕获和混合方式。它们是两种更简便的捕获方式,它们可以让编译器自动推断需要捕获的外部变量。
1> 隐式捕获:
[=]表示以值捕获的方式捕获所有外部变量(默认),成员函数包括this指针;
[&]表示以引用捕获的方式捕获所有外部变量,成员函数包括this指针。
混合方式是指在隐式捕获的基础上,显式地指定某些变量的不同捕获方式。

2> 混合使用时,要求捕获列表中第一个元素必须是隐式捕获(&或=),然后后面可以跟上显式指定的变量名和符号。
混合使用时,若隐式捕获采用引用捕获&,则显式指定的变量必须采用值捕获的方式;若隐式捕获采用值捕获=,则显式指定的变量必须采用引用捕获的方式,即变量名前加&。

一个例子:

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;
}

注意点:
1> [=]以值捕获,是默认状态一般不写。
2> 不允许以相同方式重复捕捉,例如[=, a],前者已经表示以值捕获所有外部变量,后者就重复了。但是[=, &a]是被允许的,因为它们的捕获方式不同。
3> Lambda表达式的父作用域是指定义lambda表达式的那个作用域,通常是一个函数或者一个类。Lambda表达式的块作用域是指lambda表达式本身的作用域,它是一个匿名函数,可以在其他地方调用。
4> Lambda表达式可以捕获父作用域中的局部变量,但是不能捕获其他作用域或者非局部变量。Lambda表达式捕获父作用域中的局部变量时,要求这个变量必须是声明为final的,或者实际上是final的(即不会被修改)。Lambda表达式不能在自己的块作用域中声明和父作用域中同名的参数或者局部变量。
5> Lambda表达式之间不能相互赋值,因为它们的本质是函数对象。即使从文本层面看它们的名字相同,但是底层编译器给它们取的名字是不同的。

5. 传递lambda表达式


C++传递lambda表达式的方法有以下几种:

1> 将lambda表达式转换为相应的函数指针(如果没有捕获任何变量)。使用std::function作为参数类型,它可以接受任何可调用对象(包括捕获变量的lambda表达式)。例如:

void foo(std::function<bool(int)> f) 
{// 函数动作
}foo ([] (int x) { // 传递lambda表达式return x > 0; }
); 

2> 将lambda表达式赋值给一个变量,然后将这个变量传递给函数。例如:

auto add = [] (int a, int b) {// 将lambda表达式赋值给add变量 return a + b; 
}; int result = add(1, 2); // 调用lambda表达式
bar(add); // 传递lambda表达式给变量bar

3> 使用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。

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;
}

在这里插入图片描述
通过汇编代码,可以看到编译器处理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表达式会被内联的条件是:
1> lambda表达式作为参数传递给一个内联函数
2> lambda表达式没有被保存或者传递到其他地方
3> lambda表达式没有从更外层的函数返回
如果不满足这些条件,可以使用noinline关键字来标记lambda参数,表示不要求内联。另外,编译器也会根据实现和优化策略来决定是否内联lambda表达式。

如果一个函数被调用的频率很低,甚至只有一次,那么内联属性使得它在被调用的位置展开,能减少调用函数创建栈帧的开销。lambda表达式的内联属性是默认的,只要编译器认为它可以在某处展开,那么它就像一个内嵌语句一样。

补充,lambda表达式在递归调用时能提供便利。例如我们要写一个DFS,我们知道这需要传参,然后再递归调用,然而这可以通过在函数内定义一个lambda表达式,然后在函数内调用它,就像调用普通函数一样。例如在这道题中最长公共子序列:

class Solution {
public:int longestCommonSubsequence(string text1, string text2) {int m = text1.size(), n = text2.size();function<int(int, int)> dfs = [&](int i, int j) -> int{// 如果任一字符串长度为0,LCS长度为0if (i == 0 || j == 0) return 0;// 如果字符匹配,继续检查剩余的字符串if (text1[i - 1] == text2[j - 1]) return dfs(i - 1, j - 1) + 1;// 如果字符不匹配,处理两个子问题elsereturn max(dfs(i - 1, j), dfs(i, j - 1));};return dfs(m, n);}
};

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

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

相关文章

Ubuntu服务器时间和本地时间不一致怎么解决——Linux的Local Time和RTC time

最近一直在搞大模型的相关工作&#xff0c;所以一直在用Linux服务器&#xff0c;前面的文章里也提到了&#xff0c;我用的是一台Dell PowerEdge R730xd。 但在使用中发现&#xff0c;IDRAC中的日志时间和本地时间存在时差&#xff0c;大概相关8小时。 对于技术人员&#xff0c…

数据结构:树形结构(树、堆)详解

数据结构&#xff1a;树形结构&#xff08;树、堆&#xff09;详解 一、树&#xff08;一&#xff09;树的性质&#xff08;二&#xff09;树的种类二叉树多叉树满N叉树完全N叉树 &#xff08;三&#xff09;二叉树的实现1、二叉树结构定义2、二叉树功能实现&#xff08;1&…

windows安全中心永久卸载工具分享

使用方法 博客&#xff1a;h0ck1r丶羽~从零到一 卸载工具下载链接&#xff1a; 夸克网盘分享 一路回车&#xff0c;选项Y即可 耐心等待几秒种&#xff0c;自动重启 此时打开windows安全中心&#xff0c;已经完全不能使用了&#xff0c;响应的杀毒功能也关了 往期推荐 【渗透测…

QT做一个USB HID设备识别软件

1.下载 HidApi库&#xff1a;GitHub - yigityuce/HidApi: Human Interface Device Api (HidApi) with C 2.pro文件添加 DEFINES - UNICODE LIBS -lsetupapi 3.建立三个对象 HidApi hidApi;HidDevice hidDev;//HID设备HidDeviceList devList;//HID设备列表 4.对 HID 设备进…

JavaWeb - Spring Boot

Spring 官网​​​​​Spring | Home Spring Boot Spring Boot是一个由Pivotal团队提供的开源框架&#xff0c;旨在简化Spring应用的初始搭建以及开发过程。在Spring Boot项目中&#xff0c;通常会有Controller、Service、Mapper和Entity等层次结构。下面将详细介绍这些层次的…

用 Higress AI 网关降低 AI 调用成本 - 阿里云天池云原生编程挑战赛参赛攻略

作者介绍&#xff1a;杨贝宁&#xff0c;爱丁堡大学博士在读&#xff0c;研究方向为向量数据库 《Higress AI 网关挑战赛》正在火热进行中&#xff0c;Higress 社区邀请了目前位于排行榜 top5 的选手杨贝宁同学分享他的心得。下面是他整理的参赛攻略&#xff1a; 背景 我们…

Serilog文档翻译系列(三) - 基础配置

Serilog 使用简单的 C# API 来配置日志记录。当需要外部配置时&#xff0c;可以&#xff08;慎用&#xff09;通过使用 Serilog.Settings.AppSettings 包或 Serilog.Settings.Configuration 包进行混合配置。 创建日志记录器 日志记录器是通过 LoggerConfiguration 对象创建的…

【RabbitMQ】快速上手

目 录 一. RabbitMQ 安装二. RabbitMQ 核心概念2.1 Producer 和 Consumer2.2 Connection 和 Channel2.3 Virtual host2.4 Queue2.5 Exchange2.6 RabbitMQ 工作流程 三. AMQP四. web界面操作4.1 用户相关操作4.2 虚拟主机相关操作 五. RabbitMQ 快速入门5.1 引入依赖5.2 编写生产…

stm32 8080时序驱动lcd屏幕

PSAM使用的硬件接口 PSAM读时序 PSAM写时序 相关时序 PSAM_RCRx NOR 和PSRAM控制寄存器

28. 双耳配对 - 配置

1. 概述 通过MAC地址的最后一位的奇偶来判断左右耳 2. 验证 右耳:奇数(主耳)-》BT ADDR: 12:42:22:34:34:6d 左耳:偶数(从耳)-》BT ADDR: 12:42:22:34:34:6c

TPH-YOLOv5:基于Transformer预测头的改进YOLOv5,用于无人机捕获场景的目标检测

摘要 提出了TPH-YOLOv5。在YOLOv5的基础上&#xff0c;增加了一个预测头来检测不同尺度的目标。然后用Transformer Prediction Heads&#xff08;TPH&#xff09;代替原有的预测头&#xff0c;探索自注意机制的预测潜力。还集成了卷积块注意力模型&#xff08;CBAM&#xff09;…

Gland安装与Debug

下载地址&#xff1a;https://www.jetbrains.com.cn/go/download/#sectionwindows debug官方文档: https://www.jetbrains.com/help/go/debugging-code.html 创建项目 选择新建项目 填写项目本地路径&#xff0c;以及选择go SDK 项目创建后检查项目设置 添加main包以及…

安装MySQL,navicat以及Django配置遇到的一些问题

MySQL安装问题 安装MySQL按照了此文章&#xff1a; MySQL数据库下载及安装教程&#xff08;最最新版&#xff09;_mysql下载安装-CSDN博客https://blog.csdn.net/weixin_39289696/article/details/128850498首先是遇到了starting the server红色叉号显示 按照上面文章的介绍…

Linux--IO多路复用(select,poll,epoll)

IO多路复用——select&#xff0c;poll&#xff0c;epoll IO多路复用是一种操作系统技术&#xff0c;旨在提高系统处理多个输入输出操作的性能和资源利用率。与传统的多线程或多进程模型相比&#xff0c;IO多路复用避免了因阻塞IO而导致的资源浪费和低效率问题。它通过将多个IO…

Linux awk案例

目录 1. 查询时间超过2000毫秒的请求2. 查询指定列组合出现的次数3. 统计所有文件的大小4. 获取大于指定大小的文件名&#xff0c;并按照从大到小排序5. grep指定字段后&#xff0c;使用awk列转行6. 查询第四个字段等于指定值的内容 1. 查询时间超过2000毫秒的请求 ✅log: 202…

[Leetcode 216][Medium]组合总和 III--回溯

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 对于组合问题&#xff0c;首先要想到回溯法。那么可以根据回溯法模版进行设计。 void backtrace(元素){if(满足题目要求的条件){保存目前路径/状态/结果;return;}for循环,往目前状态相邻的所…

区块链通证系统功能分析

区块链通证系统功能分析涉及多个关键方面&#xff0c;以确保系统能够满足不同的业务需求和合规性要求。 同质与非同质通证&#xff1a;区块链通证系统需要支持同质通证&#xff08;如ERC-20&#xff09;和非同质通证&#xff08;如ERC-721&#xff09;&#xff0c;以适应不同类…

《NLP自然语言处理》—— 关键字提取之TF-IDF算法

文章目录 一、TF-IDF算法介绍二、举例说明三、示例&#xff1a;代码实现四、总结 一、TF-IDF算法介绍 TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种用于信息检索与文本挖掘的常用加权技术。TF-IDF是一种统计方法&#xff0c;用以评估一个词…

机器人大会引领产业动向,卓翼飞思绘制无人系统教科研新蓝图

8月21日&#xff0c;万众瞩目的2024世界机器人大会暨博览会在北京亦创国际会展中心盛大开幕。这场为期5天&#xff0c;集“展览”“论坛”“赛事”于一体的机器人盛会&#xff0c;反映了当下机器人领域的繁荣生态。据官方统计数据&#xff0c;今年现场逛展观众高达25万人次&…

揭秘!糖尿病:从绝望到希望的治愈之路

在这个快节奏、高压力的时代&#xff0c;糖尿病这一“甜蜜的负担”正悄然影响着越来越多人的生活。面对这一全球性的健康挑战&#xff0c;许多患者心中都萦绕着一个共同的疑问&#xff1a;“糖尿病&#xff0c;真的能治好吗&#xff1f;”今天&#xff0c;就让我们一起揭开糖尿…