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

news2025/1/12 10:38:04

在这里插入图片描述

阅读导航

  • 引言
  • 一、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.coloradmin.cn/o/1240559.html

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

相关文章

新能源车将突破2000万辆,汉威科技为电池安全保驾护航

近年来&#xff0c;我国新能源汽车销量持续突破新高。据中汽协数据&#xff0c;1~10月&#xff0c;国内新能源汽车销量达728万辆&#xff0c;同比增长37.8%&#xff0c;市场占有率达到30.4%。随着第四季度车市传统旺季的到来&#xff0c;新能源消费需求将进一步释放&#xff0c…

机器学习/sklearn 笔记:K-means,kmeans++

1 K-means介绍 1.0 方法介绍 KMeans算法通过尝试将样本分成n个方差相等的组来聚类&#xff0c;该算法要求指定群集的数量。它适用于大量样本&#xff0c;并已在许多不同领域的广泛应用领域中使用。KMeans算法将一组样本分成不相交的簇&#xff0c;每个簇由簇中样本的平均值描…

图神经网络与图注意力网络

随着计算机行业和互联网时代的不断发展与进步&#xff0c;图神经网络已经成为人工智能和大数据的重要研究领域。图神经网络是对相邻节点间信息的传播和聚合的重要技术&#xff0c;可以有效地将深度学习的理念应用于非欧几里德空间的数据上。本期推送围绕图神经网络与图注意力网…

老生常谈之 JavaScript 中 0.1 + 0.2 != 0.3 的原因

先来一个模棱两可的说法&#xff1a;因为精度丢失、存储溢出的问题 先复习一下二进制的转换方法&#xff1a; 整数&#xff1a;除以基数&#xff0c;取余&#xff0c;自底向上小数&#xff1a;乘以基数&#xff0c;取整&#xff0c;自顶向下 接着&#xff0c;复习一下双精度…

QTableWidget——编辑单元格

文章目录 前言熟悉QTableWiget&#xff0c;通过实现单元格的合并、拆分、通过编辑界面实现表格内容及属性的配置、实现表格的粘贴复制功能熟悉QTableWiget的属性 一、[单元格的合并、拆分](https://blog.csdn.net/qq_15672897/article/details/134476530?spm1001.2014.3001.55…

ios qt开发要点

目前关于ios qt的开发资料比较少&#xff0c;这里整理了几个比较重要的开发要点&#xff0c;基于MacOS14 Xcode15 Qt15.5 cmake iphone真机。 cmake报错&#xff0c;报错信息如下 CMake Error at /Users/user/Qt/5.15.5/ios/lib/cmake/Qt5Core/Qt5CoreConfig.cmake:91 (m…

C++初阶 | [四] 类和对象(下)

摘要&#xff1a;初始化列表&#xff0c;explicit关键字&#xff0c;匿名对象&#xff0c;static成员&#xff0c;友元&#xff0c;内部类&#xff0c;编译器优化 类是对某一类实体(对象)来进行描述的&#xff0c;描述该对象具有哪些属性、哪些方法&#xff0c;描述完成后就形成…

shell脚本之循环语句(for、while、untli)

循环语句&#xff1a; 一定要有跳出循环条件 循环条件&#xff1a; 1.已知循环的次数&#xff08;新来十个人&#xff0c;就要新建十个账号 2.未知循环的次数&#xff0c;但是要有跳出循环条件&#xff08;对象生气&#xff0c;要道歉到原谅为止&#xff09; for&#xff…

SpringBoot_websocket实战

SpringBoot_websocket实战 前言1.websocket入门1.1 websocket最小化配置1.1.1 后端配置1.1.2 前端配置 1.2 websocket使用sockjs1.2.1 后端配置1.2.2 前端配置 1.3 websocket使用stomp协议1.3.1 后端配置1.3.2 前端配置 2.websocket进阶2.1 websocket与stomp有什么区别2.2 webs…

【ThingJS】类型转换以及注册

前言 目前国家提倡加快数字化发展&#xff0c;建设数字中国&#xff0c;并于今年2月份中共中央、国务院印发的《数字中国建设整体布局规划》中明确&#xff0c;数字中国建设按照“2522”的整体框架进行布局。其中提到“构建以数字孪生流域为核心的智慧水利体系”&#xff0c;可…

HarmonyOS(三)—— 应用程序入口—UIAbility

前言 学习过android的同学都是知道Activity&#xff0c;Activity是Android组件中最基本也是最为常见用的四大组件之一&#xff0c;用户可以用来交互为了完成某项任务。 Activity中所有操作都与用户密切相关&#xff0c;是一个负责与用户交互的组件&#xff0c;可以通过setCon…

Mac自带的看图如何连续查看多张图片

一、问题 mac看访达里的图片时&#xff0c;双击打开一张图片&#xff0c;然后按上下左右键都没法切换到另外的图片。而且也没找到像window一样单击缩略图可以看到预览图。其实是自己不懂得怎么使用&#xff0c;哈哈哈&#x1f602; 二、方法 2.1、图标方式 可以看到缩略图&a…

原理Redis-QuickList

QuickList **问题1&#xff1a;**ZipList虽然节省内存&#xff0c;但申请内存必须是连续空间&#xff0c;如果内存占用较多&#xff0c;申请内存效率很低。怎么办&#xff1f; 为了缓解这个问题&#xff0c;我们必须限制ZipList的长度和entry大小。 **问题2&#xff1a;**但是…

【NLP】GPT 模型如何工作

介绍 2021 年&#xff0c;我使用 GPT 模型编写了最初的几行代码&#xff0c;那时我意识到文本生成已经达到了拐点。我要求 GPT-3 总结一份很长的文档&#xff0c;并尝试了几次提示。我可以看到结果比以前的模型先进得多&#xff0c;这让我对这项技术感到兴奋&#xff0c;并渴望…

[数据结构]-AVL树

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

陪玩圈子系统APP小程序H5,详细介绍,源码交付,支持二开!

陪玩圈子系统&#xff0c;页面展示&#xff0c;源码交付&#xff0c;支持二开&#xff01; 陪玩后端下载地址&#xff1a;电竞开黑陪玩系统小程序&#xff0c;APP&#xff0c;H5: 本系统是集齐开黑&#xff0c;陪玩&#xff0c;陪聊于一体的专业APP&#xff0c;小程序&#xff…

Github Copilot AI编码完成工具

目录 一、GitHub Copilot 1、简介 2、工作原理 3、功能 二、GitHub Copilot X 1、什么是 GitHub Copilot X 2、GitHub Copilot X 的功能 三、支持、使用 1、支持 2、使用 四、实际研究、验证(代码方向) 1、代码生成 2、代码提示 3、生成测试用例 4、代码解释 5…

排序算法--归并排序

实现逻辑 ① 将序列每相邻两个数字进行归并操作&#xff0c;形成floor(n/2)个序列&#xff0c;排序后每个序列包含两个元素 ② 将上述序列再次归并&#xff0c;形成floor(n/4)个序列&#xff0c;每个序列包含四个元素 ③ 重复步骤②&#xff0c;直到所有元素排序完毕 void pri…

Rust并发编程:理解线程与并发

大家好&#xff01;我是lincyang。 今天我们来深入探讨Rust中的并发编程&#xff0c;特别是线程的使用和并发的基本概念。 Rust中的线程 Rust使用线程来实现并发。线程是操作系统可以同时运行的最小指令集。在Rust中&#xff0c;创建线程非常简单&#xff0c;但与此同时&…

SHAP - 机器学习模型可解释性工具

github地址&#xff1a;shap/docs/index.rst at master shap/shap (github.com) SHAP使用文档&#xff1a;欢迎使用 SHAP 文档 — SHAP 最新文档 SHAP介绍 SHAP&#xff08;SHapley Additive exPlanations&#xff09;是一种用于解释预测结果的方法&#xff0c;它基于Shapley…