【C++入门到精通】 Lambda表达式 C++11 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、C++98中的一个例子
  • 二、Lambda表达式
    • 1. Lambda表达式语法
      • (1)Lambda表达式各部分说明
      • (2)捕获列表说明
  • 三、Lambda表达式的底层原理
  • 温馨提示

引言

当今软件开发行业的快速发展和日益复杂的需求,要求程序员们具备灵活而高效的编程技巧。在这样的背景下,C++11引入了一项强大而令人兴奋的特性:lambda表达式。lambda表达式为C++程序员提供了一种简洁、灵活且强大的方式来定义和使用匿名函数。通过lambda表达式,我们可以将函数作为一等公民对待,更加方便地实现函数对象的传递和使用。它不仅提供了一种新的编码方式,还使得代码更易于理解和维护。

在本文中,我们将深入探讨C++11中lambda表达式的语法、特性和用法。我们将介绍如何定义lambda表达式,如何捕获外部变量,并演示lambda表达式在各种场景下的实际应用。无论您是C++开发新手还是有经验的老手,本文都将为您提供全面而深入的指导,帮助您充分发挥lambda表达式的威力,提升代码的可读性和性能。请各位坐稳扶好,咱们要开车了😍!!!

一、C++98中的一个例子

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::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 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
}

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一algorithm算法,都要重新去写一个类,感觉就为了一盘醋包了饺子。如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

二、Lambda表达式

int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };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; });
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数。是不是感觉目的明朗了好多?这就是lambda表达式的魅力。

1. Lambda表达式语法

lambda表达式的书写格式为:

[capture-list] (parameters) mutable -> return-type { statement 
}

(1)Lambda表达式各部分说明

⭕其中各部分的含义如下:

  • capture-list捕获列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • parameters:参数列表,类似于普通函数的参数列表用于接收传入的参数。如果不需要参数传递,则可以连同( )一起省略
  • mutable:可选关键字,用于指示是否可以修改被捕获的值。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
  • return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

🚨注意
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空因此C++11中最简单的lambda函数为:[ ] { }; 该lambda函数不能做任何事情

int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; };fun1(10)cout<<a<<" "<<b<<endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int{return b += a+ c; };cout<<fun2(10)<<endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

(2)捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var,即在lambda表达式内部以值的方式使用外部变量var。
  • [=]:表示值传递方式捕获所有父作用域中的变量,包括this指针。在lambda表达式内部以值的方式使用所有外部变量。
  • [&var]:表示引用传递捕捉变量var,即在lambda表达式内部以引用的方式使用外部变量var。
  • [&]:表示引用传递捕捉所有父作用域中的变量,包括this指针。在lambda表达式内部以引用的方式使用所有外部变量。
  • [this]:表示值传递方式捕捉当前的this指针,可以在lambda表达式内部使用当前对象的成员变量和成员函数。

通过这些捕获方式,可以灵活地控制lambda表达式对外部变量的访问方式,从而实现不同的功能需求。

🚨注意

  1. 父作用域指的是包含lambda函数的语句块或函数体
  2. 捕获列表可以由多个捕获项组成,并以逗号分隔。每个捕获项可以使用不同的捕获方式,如值传递或引用传递。
    • 例如 [=, &a, &b] 表示以引用传递的方式捕捉变量a和b,以值传递的方式捕捉其他所有变量。
    • 例如 [&, a, this] 表示以值传递的方式捕捉变量a和this,以引用传递的方式捕捉其他变量。
  3. 捕获列表不允许重复捕捉同一个变量,否则会导致编译错误
    • 例如 [=, a] 中的= a 重复捕捉了变量a。
  4. 在不是块作用域的地方定义的lambda函数,捕获列表必须为空。
  5. 在块作用域中定义的lambda函数只能捕捉父作用域中的局部变量,捕捉其他作用域或非局部变量将导致编译错误。
  6. lambda表达式之间不能相互赋值,即使看起来类型相同
void (*PF)();
int main()
{auto f1 = []{cout << "hello world" << endl; };auto f2 = []{cout << "hello world" << endl; };//f1 = f2; // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

三、Lambda表达式的底层原理

⭕函数对象

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象

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

从使用方式上来看,函数对象与lambda表达式完全一样实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator( )
在这里插入图片描述

当我们使用Lambda表达式时,实际上是在定义一个匿名函数对象。这个匿名函数对象可以捕获其所在作用域中的变量,并且可以像函数一样被调用。Lambda表达式的底层原理涉及到以下几个步骤:

  1. 闭包类型的生成:编译器会根据Lambda表达式的定义,隐式地生成一个与Lambda表达式对应的闭包类型。这个闭包类型实际上是一个匿名的类类型,它包含Lambda表达式的执行体,以及捕获的外部变量。

  2. 捕获列表的处理:Lambda表达式可以通过捕获列表指定在其作用域外部定义的变量的访问方式。捕获列表告诉编译器应该以值传递还是引用传递的方式来捕获变量,并且根据捕获列表生成对应的闭包类型的成员变量。

  3. 闭包对象的生成:当Lambda表达式被创建时,实际上是生成了一个对应的闭包对象,同时也会生成对应的闭包类型。这个闭包对象包含了Lambda表达式的执行体,以及捕获的外部变量的值或引用。

  4. 函数调用操作符的重载:生成的闭包类型重载了函数调用操作符operator(),并且在其中实现了Lambda表达式的执行体。当我们调用Lambda表达式时,实际上是在调用这个闭包对象的operator(),从而执行Lambda表达式的代码。

通过以上步骤,Lambda表达式在底层实际上是通过生成闭包类型、处理捕获列表、生成闭包对象,并重载函数调用操作符来实现的。这些机制使得Lambda表达式能够在C++中方便地定义匿名函数,并捕获其所在作用域中的变量。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

C运算符与表达式

跟着肯哥&#xff08;不是我&#xff09;学运算符与表达式 运算符 在C语言中&#xff0c;运算符是一种用来执行特定操作的符号或关键字。它们用于对变量、常量和表达进行计算、逻辑判断和位操作等。 定义一般都当耳旁风了 运算符分类 算术运算符 -*/%加减乘除取模&#xff0c;…

everything排除目录

everything默认搜索所有文件&#xff0c;自己把没啥必要的目录都屏蔽掉&#xff0c;记录如下

ChatGPT/GPT4丨编程助手;AI画图;数据分析;科研/项目实现;提示词工程技巧;论文写作等

ChatGPT 在论文写作与编程方面也具备强大的能力。无论是进行代码生成、错误调试还是解决编程难题&#xff0c;ChatGPT都能为您提供实用且高质量的建议和指导&#xff0c;提高编程效率和准确性。此外&#xff0c;ChatGPT是一位出色的合作伙伴&#xff0c;可以为您提供论文写作的…

[数据结构]-AVL树

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、AVL树基…

IBM ELM—系统工程全生命周期管理平台

产品概述 Engineering Lifecycle Management是IBM提供的工程全生命周期管理组合工具&#xff0c;帮助企业降低开发成本&#xff0c;应对开发挑战并更快地发展其流程和实践。 随着产品变得更加复杂且数字化&#xff0c;传统的工程开发不再能及时且有效地满足系统工程的复杂度&a…

【Django-DRF】多年md笔记第5篇:Django-DRF的Request、Response和视图详解

本文从分析现在流行的前后端分离Web应用模式说起&#xff0c;然后介绍如何设计REST API&#xff0c;通过使用Django来实现一个REST API为例&#xff0c;明确后端开发REST API要做的最核心工作&#xff0c;然后介绍Django REST framework能帮助我们简化开发REST API的工作。 Dj…

[点云分割] 基于最小切割的分割

效果&#xff1a; 代码&#xff1a; #include <iostream> #include <vector>#include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/cloud_viewer.h> #include <pcl/filters/filter_indices.h> #include…

Can‘t open the append-only file: Permission denied

redis rdb aof-CSDN博客 Cant open the append-only file: Permission denied E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit\redis.conf 还是不行&#xff0c;就要修改权限了&#xff0c;windows【完全控…

matlab 最小二乘拟合平面并与XOY平面对齐

目录 一、算法原理二、代码实现1、绕原点对齐2、绕质心对齐三、结果展示1、绕原点对齐2、绕质心对齐四、测试数据本文由CSDN点云侠原创,原文链接。爬虫网站自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 首先,使用最小二乘拟合平面…

priority_queue简单实现(优先级队列)(c++)

priority_queue priority_queue介绍逻辑实现框架调整算法adjust_up()adjust_down() 仿函数/比较函数仿函数特性 构造函数迭代器区间构造 完整优先级队列代码 priority_queue介绍 pri_que是一个容器适配器&#xff0c;它的底层是其他容器&#xff0c;并由这些容器再封装而来。类…

C语言指针相关练习题

​ C语言指针相关练习题 文章目录 C语言指针相关练习题题目一题目二题目三题目四题目五题目六题目七 题目一 #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int *ptr (int *)(&a 1);printf( "%d,%d", *(a 1), *(ptr - 1));return 0; }…

[Unity+OpenAI TTS] 集成openAI官方提供的语音合成服务,构建海王暖男数字人

1.简述 最近openAI官方发布了很多新功能&#xff0c;其中就包括了最新发布的TTS语音合成服务的api接口。说到这个语音合成接口&#xff0c;大家可能会比较陌生&#xff0c;但是说到chatgpt官方应用上的聊天机器人&#xff0c;那个台湾腔的海王暖男的声音&#xff0c;可能就有印…

轻量封装WebGPU渲染系统示例<38>- 动态构建WGSL材质Shader(源码)

实现原理: 基于宏定义和WGSL功能文件实现 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/DynamicShaderBuilding.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#x…

前缀和及差分数组

前缀和 原数组x0x1x2x3x4x5前缀和数组x0x0x1x0x1x2x0x1x2x3x0x1x2x3x4x0x1x2x3x4x5前缀和数组代数形式x0’x1’x2’x3’x4’x5’ 计算原数组某区间的和 sum[x1,x2,x3] 利用前缀和计算 x3-x0 x0x1x2x3-x0 x1x2x3 差分数组 x0x1x2x3x4x5原数组x0x1x2x3x4x5差分数组x0x1-x0x…

HTTP四大参数类型及请求参数的方式和如何接收

HTTP 请求中4大参数类型和接收方法。 1、请求头参数head 请求头参数顾名思义&#xff0c;是存放在请求头中发送给服务器的参数&#xff0c;服务器通过解析请求头获取参数内容。通常会存放本次请求的基本设置&#xff0c;以帮助服务器理解并解析本次请求的body体。 参数形式如…

srs的webrtc信令分析

关于webrtc的流信令只有四个 /rtc/v1/publish/&#xff0c;这是推流接口&#xff0c;是推流客户端跟SRS交换SDP的接口 /rtc/v1/play/&#xff0c;这是拉流接口&#xff0c;是拉流客户端跟SRS交换SDP的接口 /rtc/v1/whip/&#xff0c;这也是推流接口&#xff0c;作用是也是交换…

C#开发的OpenRA游戏之属性RenderSprites(8)

C#开发的OpenRA游戏之属性RenderSprites(8) 本文开始学习RenderSprites属性,这个属性是跟渲染有关的,因此它就摄及颜色相关的内容,所以我们先来学习一下调色板,这是旧游戏的图片文件保存的格式,如果放在现代来看,不会再采用这种方法,毕竟现在存储空间变大,便宜了,并…

idea编译问题导致接口调用不通

问题背景&#xff1a; 1.idea版本2021&#xff0c;springboot&#xff0c;父子maven项目&#xff0c;创建了一个新的model。启动之后&#xff0c;调试controller接口&#xff0c;接口一直报404。 问题分析&#xff1a; 1.查看编译后的文件&#xff0c;发现java代码一直没编译…

Vue3使用dataV报错问题解决

DataV官网&#xff1a;https://datav-vue3.jiaminghi.com/guide/ vue2中是没有问题的&#xff0c;这是第一次在vue3中使用发现的报错问题 报错问题 首先安装&#xff1a; pnpm add dataview/datav-vue3 1. 全局注册报错 然后main.ts全局注册 import { createApp } f…

html网站-关于发展历程的案例

一、案例一 1.效果图&#xff1a; 2.代码&#xff1a; 所用到的文件自行在官网下载&#xff0c;也可在git上拉取。 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta…