【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表达式没有任何意义
	[]{};
	
	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int 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;
	
	// 复制捕捉x
	int 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);
	
	// lambda
	auto 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.mfbz.cn/a/179030.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

No matching variant of com.android.tools.build:gradle:7.4.2 was found.

一、报错信息 创建个新项目&#xff0c;运行直接报错&#xff0c;信息如下&#xff1a; No matching variant of com.android.tools.build:gradle:7.4.2 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar,…

高防服务器的工作原理

在当今互联网时代&#xff0c;网络安全问题日益突出&#xff0c;各种网络攻击层出不穷。为了保护企业的网络安全&#xff0c;高防服务器应运而生。那么&#xff0c;你是否了解高防服务器的工作原理呢&#xff1f;下面就让我们一起来探索一下。 高防服务器是一种能够有效抵御各种…

DRF-项目-(1):构建纯净版的drf项目,不再使用django的后台管理,django的认证,django的session等功能,作为一个纯接口项目

项目的目录结构&#xff1a; -HeartFailure |-- apps |--user |--HeartFailure |-- static |--manage.py 一、django项目相关的 1、命令行中创建django项目 #1、切换到指定的虚拟环境中 workon my_drf#2、该虚拟环境已经安装好django和rest_framework了 django-admin startp…

Matlab进阶绘图第33期—双曲面图

在《Matlab论文插图绘制模板第56期—曲面图&#xff08;Surf&#xff09;》中&#xff0c;我分享过曲面图的绘制模板。 然而&#xff0c;有的时候&#xff0c;需要在一张图上绘制两个及以上的曲面图&#xff0c;且每个曲面图使用不同的配色方案。 在Matlab中&#xff0c;一张…

const修饰

const 起保护作用&#xff0c;禁止修改。 此时a变为常量&#xff0c;常量不可修改。 const放在*p的左端限制*p&#xff0c;即不能通过修改指针变量&#xff08;*p&#xff09;的值来修改p指向空间的内容&#xff0c;但p不受限制。 const放在*的右端限制p&#xff0c;即不能修…

【10套模拟】【8、9】

关键字&#xff1a; 建立有序单链表、多维数组是特殊线性结构、直接选择排序、哈夫曼树高度及wpl、中序最后叶子先序最后也是、堆是完全二叉树、分块查找 统计二叉树结点数、统计二叉树值和、奇偶数划分、判断链表是否递增

RPG项目_UI登录

首先创建一个项目 将资源包导进Resources文件夹 创建一个Scripts脚本文件夹 然后再对Scripts脚本文件夹分门别类 导入UI资源包 创建一个Image 按住Alt 选择右下角 image就会覆盖整个面板 修改image名字为BG 将image图片放置背景栏 再创建一个image 改名为MainMenu 修改MainMenu…

josef约瑟 电压继电器DJ-132 100~200V柜内板前接线,带附件

DJ-100系列电压继电器 DJ-111电压继电器&#xff1b; DJ-112电压继电器&#xff1b; DJ-121电压继电器&#xff1b; DJ-122电压继电器&#xff1b; DJ-131电压继电器&#xff1b; DJ-132电压继电器&#xff1b; DJ-131/60C电压继电器&#xff1b; DJ-131/60CN电压继电器…

【JavaEE初阶】认识线程、创建线程

1. 认识线程&#xff08;Thread&#xff09; 1.1 概念 1) 线程是什么 一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码. 举例&#xff1a; 还是回到我们之前的银⾏的例⼦中。之前我们主要描…

【libGDX】使用Mesh绘制矩形

1 前言 使用Mesh绘制三角形 中介绍了绘制三角形的方法&#xff0c;本文将介绍绘制正方形的方法。 libGDX 以点、线段、三角形为图元&#xff0c;没有提供绘制矩形内部的接口。要绘制矩形内部&#xff0c;必须通过三角形拼接而成&#xff0c;如下图&#xff0c;是通过GL_TRIANGL…

srs的webrtc信令分析

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

Qt全球峰会2023中国站 参会概要

Qt全球峰会2023中国站 参会概要 前言峰会议程签到 & Demo 演示开场致辞Qt Group 产品总监演讲&#xff08;产品开发的趋势-开放的软件、工具和框架&#xff09;产品战略QtQuick or QtWidgets&#xff08;c or qml&#xff09;Qt如何定义AI个人看法 Qt 在券商数字化转型和信…

风电场叶片运输车模型-FBX格式-带动画-数字孪生场景搭建

FBX格式的风电场中叶片运输车辆模型&#xff0c;按照真实尺寸建模&#xff0c;车辆多个部位带动画效果&#xff0c;适用于风电场三维数字化场景和风电场数字孪生使用&#xff0c;也可以用来作为各种三维平台的测试模型。 模型效果图 下载地址 叶片运输车模型下载地址

专访|OpenTiny 开源社区 常浩:完成比完美更重要

前言 2023年已过大半&#xff0c;备受关注的 OpenTiny*开源之夏活动也顺利结项。开源之夏由中国科学院软件研究所发起的计划&#xff0c;目的在于鼓励在校学生积极参与开源软件的开发维护&#xff0c;推动优秀开源软件社区的繁荣发展。该活动联合各大开源社区&#xff0c;聚焦…

逸学java【初级菜鸟篇】9.4 泛型

hi&#xff0c;我是逸尘&#xff0c;一起学java吧 泛型概述 泛型是我们在定义某一个类型规格的时候使用的泛指&#xff0c;我们预先定义一个大方向&#xff0c;防止路线错误。 实质上是程序员定义的安全类型&#xff0c;Object是顶级父类&#xff0c;在没有泛型很多程序员为了…

Android 10.0 mtp模式下连接PC后只显示指定文件夹功能实现

1. 前言 在android10.0的系统定制化开发中,对于usb作为otg连接电脑时,在mtp模式下会作为一个存储器在电脑端显示,作为电脑的 一个盘符,来显示设备的内部存储的文件,所以说如果要对设备内部的资料做保密处理的时候,需要在mtp模式下不显示某些 文件夹,接下来就分析下相关…

【C语言】qsort函数

目录 简介 头文件 ​编辑 函数原型&#xff1a; 参数函数如何写&#xff1a; 参数函数要求&#xff1a; qsort对整性数据的排序&#xff1a; qsort对字符型数据的排序&#xff1a; 对结构体类型的内部元素排序&#xff1a; 函数的底层是以快速排序实现的 但是本文不深入…

office word 使用笔记

office word 使用笔记 1. 功能1.1 格式快捷键1.2 复选框 2 遇到过的问题2.1 表格标题和表格距离过大 1. 功能 1.1 格式快捷键 复制格式&#xff1a;ctrl shift c 粘贴格式&#xff1a;ctrl shift v 1.2 复选框 方框位置和类型&#xff1a;“插入——高级符号——字体”选…

鸿蒙4.0开发笔记之ArkTs语言基础与基本组件结构(四)

文章声明&#xff1a;本文关于HarmonyOS系统的部分内容和描述借鉴于华为官网的“HarmonyOS开发者学堂”&#xff0c;有需要的也可以进入官网查看。<HarmonyOS第一课>ArkTS开发语言介绍 一、ArkTs语言介绍 ArkTS是鸿蒙系统&#xff08;HarmonyOS&#xff09;优选的主力应…

如何访问linux上的web服务

1.获取服务运行端口 例如8080 2.如果时vmware 需要先配置转发端口和主机ip 主机ip需要未使用的 例如&#xff1a; 3.查看虚拟机防火墙设置 centos8 为例 &#xff1a; firewall-cmd --zonepublic --list-ports 查看放通端口 如果没有放通 firewall-cmd --zonepublic --add-p…
最新文章