C++技能 - 详解使用Lambda表达式【再也不怕看不懂别人的代码了,干货还是蛮多的】

news2025/1/24 5:30:32

系列文章目录

C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程
C++技能系列

期待你的关注哦!!!
在这里插入图片描述

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

详解使用Lambda表达式

  • 系列文章目录
  • 一、lambda表达式 - 定义
  • 二、lambda表达式 - 捕获列表
  • 三、lambda表达式 - 延时调用易出错细节分析
  • 四、lambda表达式 - 如何使用mutable
  • 五、lambda表达式 - 作为匿名的类类型对象
  • 六、lambda表达式 - 在for_each和find_if中使用
  • 七、小结

lambda表达式是C++11引入的一个很重要的的特性, lambda表达式也是 一个可调用对象,它定义了一个 匿名函数,并且可以 捕获一定范围内的变量

一、lambda表达式 - 定义

lambda表达式一般形式:

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

auto f = [](int a) -> int{
	return a + 1;
}
std::cout << f(1) << std::endl;

(1) 参数列表也可以有默认值:

auto f = [](int a = 6) -> int{
	return a + 1;
}
std::cout << f(1) << std::endl;

(2) 没有参数的时候,参数列表可以省略,甚至"()"也可以省略,所以如下代码是合法的:

auto f1 = ()[]{return 1;};
auto f2 = []{return 2;};
std::cout << f(1) << std::endl;
std::cout << f(2) << std::endl;

(3) 捕获列表[ ]和函数体不能省略,必须时刻包含。
(4) lambda表达式的调用方法和普通函数相同,都是使用"( )"这种函数调用运算符。
(5) lambda表达式可以不返回任何类型,返回任何类型就是返回void。
(6) 函数体末尾的分号不能省。

二、lambda表达式 - 捕获列表

lambda表达式通过捕获列表捕获一定范围内的变量,那么,这个范围究竟是什么意思呢?

(1) [ ]: 不捕获任何变量

看如下范例:

int i = 9;
auto f1 = []{
	//报错(无法捕获外部变量),不认识这个i在哪里定义,
	//看来lambda表达式毕竟是匿名函数,按常规理解是不行。
	return i;
};

⚠️ 但不包括静态局部变量,lambda可以直接使用静态局部变量。例如,上面的int i = 9;修改为static int i = 9; 是可以在lambda表达式中使用的。

(2) [&]: 捕获外部作用域中所有变量,并作为引用在函数体内使用

看如下范例:

int i = 9;
auto f1 = [&]{
	//因为&的存在,允许给i赋值,从而也就改变了i的值
	i = 5;
	return i;
};
//5,调用了lambda表达式,所以i的发生改变
std::cout << f1() << std::endl;
//5,i值发生改变,现在i=5
std::cout << i < std::endl; 

⚠️ 既然引用,那么在调用这个lambda表达式的时候,就必须确保该lambda表达式里的引用的变量没有超过这个变量的作用域(保证有效性)。

(3) [=]: 捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,也就可以用它的值,但不能给它赋值

看如下范例:

int i = 9;
auto f1 = [=]{
	//这就非法了,不可以给它赋值,因为是以值方式捕获
	//使用该值(返回该值),就可以
	//i = 5;
	return i;
}
//9, 调用了lambda表达式
std::cout << f1() << std::endl;

⚠️ 不可以给它赋值,因为是以值方式捕获,使用该值(返回该值),就可以。

(4) [this]:一般用于类中,捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限。如果已经使用了"&“或者”=",则默认添加了此项(this项)。也就是说,捕获this的目的就是在lambda表达式中使用当前类的成员函数和成员变量。

看如下范例:

class CT{
	public:
		int m_i = 5;
		void myfuncpt(int x, int y){
			//无论用this还是&,=都可以读取成员变量的值
			auto mylambda1 = [this]{//是获取不到形参x,y的值的
				//有this,这个访问才合法,有&、=也可以
				return m_i;
			};
			std::cout << mylambda1() << std::endl;
		}
};
//main函数使用如下:
CT ct;
//5
ct.myfuncpt(3, 4);

⚠️ 针对成员变量,[this] 或者[=]可以读取,但不可以修改,如果想修改,可以使用[&]。

(5) [变量名]:按指捕获 和 [& 变量名]:按引用捕获:

[变量名]:按值捕获(不能修改)变量名所代表的变量,同时不能捕获其他变量。
[& 变量名]:按引用捕获(可以修改)变量名所代表的变量,同时不能捕获其他变量。

在前面CT类的myfuncpt成员函数中,因为没有捕获形参x和y的值,所以无法在lambda表达式中使用形参x和y。
如果lambda表达式使用x和y的值,可以如下修改:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [this, x, y]{...};

也可以修改如下这样:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [=]{...};
//可以在lambda表达式中修改x,y值
auto mylambda1 = [&]{...};

对于按引用捕获变量名所代表的变量,看看如下范例:

//只可以使用修改x的值
auto mylambda1 = [&x]{...};
//只可以使用修改x和y的值
auto mylambda1 = [&x, &y]{...};

(6) [=, & 变量名]:按值捕获所有外部变量,但按引用&中所指的变量,这里的=必须写在开头的位置,开头的位置表示默认捕获的方式

看如下CT类的myfuncpt成员函数中的lambda表达式:

auto mylambda1 = [this, &x, y]{
	x = 8;
	...
	return m_i;
}

⚠️ auto mylambda1 = [this, &x, y] 也可以写成 auto mylambda1 = [=, &x]也可以。

(7) [&, 变量名]:按引用捕获所有外部变量,但按值捕获变量名所代表的变量。这里的&必须写在开头的位置,开头的位置表示默认捕获的方式

下面这样是不行的:

//这样不行,开始制定了默认捕获,后来又指定引用捕获,编译器汇报错
auto f = [&, &x]{...}

修改为正确如下:

auto f = [&, x]{...}

三、lambda表达式 - 延时调用易出错细节分析

看如下范例:

int x = 5;
auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了
	return x;
};
x = 10;
//5, return的x是5而不是10
std::cout << f() << std::endl;

⚠️ auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了,所以打印的是5而不是10。

那怎么办呢?
办法是按引用方式捕获:

auto f = [&]{...}

四、lambda表达式 - 如何使用mutable

mutable(易变的)并不陌生,mutable关键字,它的作用就是不管是不是一个常量属性的变量,只要mutable在,就能修改其值。

int x = 5;
auto f = [=]() mutable {
	//没有mutable,这个x是不允许修改的
	x = 6;
	return x;
};
x = 10;
//6, return 的x是6而不是10
std::cout << f() << std::endl;

⚠️ 正常lambda表达式没有参数时()是可以省略的,但是如果要是使用mutable,lambda表达式中就算没有参数时()是也不可以省略()的,必须写出来。

下面是不合法的:

auto f = [=] mutable {...}; //即便没有参数,也不可以把mutable前面的()省略

五、lambda表达式 - 作为匿名的类类型对象

lambda表达式的类型被称为闭包类型。闭包先理解成:函数内的函数(可调用对象)。

这个lambda表达式是一种比较特殊的、匿名的、类型(闭包类)的对象,也就是说有定义了一个类类型,有生成一个匿名的该类的对象(闭包)。可以认为它是一个带有operator()的类类型对象,也就是仿函数(函数对象)或者说是可调用对象。

所以,也可以使用std::function和std::bind来保存和调用lambda表达式。每个lambda都会出发编译器生成一个独一无二的类类型(及所返回的该类类型对象)。

(1)lambda表达式在std::function的使用

看如下两个范例:

范例1:

std::function<int(int)> fc1 = [](int tv){return tv;}
std::cout << fc1(15) << std::endl; //15

范例2:

std::vector<std::function<bool(int)>> gv;
void func(){
	srand((unsigned)time(NULL));
	int tmpvalue =  rand % 6
	gv.push_back([=](int tv){  //如果是引用[&],会不会造成未定义行为?思考一下。
		if (tv % tmpvalue == 0)
			return true;
		return false;
	});
}
int main(){
	func();
	std::cout << gv[0](10) << std::endl;
}

(2)lambda表达式在std::bind的使用

看如下范例:

//bind第一个参数是函数指针,第二个参数开始就是真正的函数参数
std::function<int(int)> fc2 = std::bind([](int tv){return tv;}, std::placeholders::_1);
std::cout << fc2(15) << std::endl; //15

在不捕获任何变量,也就是捕获列表为空(因为类是有this的概念,普通函数是没有这个概念的),lambda表达式可以转换成一个普通的函数指针,看如下范例:

using functype = int (*)(int); //定义一个函数指针类型
functype fp = [](int tv){return tv;};
std::cout << fp(17) << std::endl;  //17

六、lambda表达式 - 在for_each和find_if中使用

(1)for_each中lambda表达式

for_each 其实是一个函数模版,一般是用来配合函数对象使用的,第三个参数就是一个函数对象(可以给进去一个 lambda 表达式)。

看如下范例:

    std::vector<int> myvector = { 10, 20, 30, 40, 50};
    int isum = 0;
    std::for_each(myvector.begin(), myvector.end(), [&isum](int value){
        isum += value;
        std::cout << value << std::endl;
    });
    std::cout << "sum = " << isum << std::endl;

输出结果:

10
20
30
40
50
sum = 150

(2)find_if中lambda表达式

find_if 其实也是一个函数模版,一般用来查找一个什么东西,要查什么取决于他的第三个参数,第三个参数也是一个函数对象(也可以给进去一个 lambda 表达式)。

看如下范例:
只要返回false, find_if 就不停地遍历myvector,一直返回true为止。

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
        std::cout << value << std::endl;
        //只要返回false, find_if就不停地遍历myvector,一直返回true为止
        return false;
    });

利用 find_if返回 true 停止这个特性,就可以寻找myvector中第一个值”>15“的元素。
⚠️ find_if的调用返回一个迭代器,只向第一个满足条件的元素。如果这样的元素不存在,则这个迭代器会指向myvector.end()。
修改后代码如下:

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
        if (value > 15)
            return true;
        return false;
    });
    if (result == myvector.end())
        std::cout << "没找到" << std::endl;
    else 
        std::cout << "没找到了, 结果为:" << *result<< std::endl; //找到了,结果为20

七、小结

lambda的优点:
善用lambda,让代码更简洁、更灵活、更强大、提高开发效率、可维护性等。

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

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

相关文章

C语言之函数初阶(2)

目录 1. 函数是什么 2. 库函数 3. 自定义函数 4. 函数参数 5. 函数调用 6. 函数的嵌套调用和链式访问 7. 函数的声明和定义 8. 函数递归 上一篇博客我们讲解了函数的前六个比较容易理解的部分&#xff0c;这一篇博客我们来讲解最后两个部分 在讲这篇博客之前&am…

Shell免交互操作

目录 一、Here Document 免交互 1.免交互定义 2.格式和使用方法 二、Expect 免交互 1.简介 2.格式和使用 &#xff08;1&#xff09;声明解释器 &#xff08;2&#xff09;spawn&#xff08;跟踪&#xff09; &#xff08;3&#xff09;expect&#xff08;期望&#x…

SpringBoot整合minio服务(超详细)

一、使用docker部署minio 1、拉取镜像 docker pull minio/minio 2、创建目录 mkdir -p /home/minio/config mkdir -p /home/minio/data 3、创建Minio容器并运行 docker run -p 9000:9000 -p 9090:9090 \--nethost \--name minio \-d --restartalways \-e "MINIO_ACC…

基于OpenCV的自动报靶识别实验

基于OpenCV的自动报靶识别实验 问题方案实验结论 问题 户外胸环靶自动报靶问题&#xff0c;目前是通过声电等方式来识别&#xff0c;成本较高&#xff0c;本文尝试使用图像处理的方法来识别。 方案 前提&#xff1a;固定相机 确定靶子的四个顶点&#xff1a;目前使用人工手…

使用imp和exp命令对orcale进行导入和导出

docker 进行orcale 查看这篇文章 https://editor.csdn.net/md/?articleId131026846 1、进入orcale 1、进入orcale容器 docker exec -it oracle11g bash2、orcale连接sysdba用户 进入root su root密码&#xff1a;helowin切换到oracle用户 su oracle使用sqlplus登录test用户…

chatgpt赋能python:Python中如何删除字符串中某个字符

Python中如何删除字符串中某个字符 Python是一种功能强大的编程语言&#xff0c;许多开发人员喜欢使用它来编写应用程序。字符串是Python中的常见数据类型之一&#xff0c;可用于存储文本。有时&#xff0c;我们可能需要删除字符串中的某个字符。本文将介绍如何在Python中使用…

GDB的学习

目录&#xff1a; 什么是gdb&#xff1f;gdb的安装gdb的使用 gdb的一些骚操作watch命令的使用调试core文件 什么是gdb&#xff1f; gdb的全称是GNU debugger&#xff0c;看名字就知道 gdb 是用来对程序进行 debug 的&#xff0c;不管是学习还是工作中&#xff0c;用好gdb&…

redis五种数据类型具体时候的底层编码

redis随着值的类型不同&#xff0c;其在底层编码类型会不相同。目前现有的编码格式有 #define OBJ_ENCODING_RAW 0 /* Raw representation */ #define OBJ_ENCODING_INT 1 /* Encoded as integer */ #define OBJ_ENCODING_HT 2 /* Encoded as hash table */ #def…

RabbitMQ集群部署之普通模式

1.集群分类 在RabbitMQ的官方文档中&#xff0c;讲述了两种集群的配置方式&#xff1a; 普通模式&#xff1a;普通模式集群不进行数据同步&#xff0c;每个MQ都有自己的队列、数据信息&#xff08;其它元数据信息如交换机等会同步&#xff09;。例如我们有2个MQ&#xff1a;m…

chatgpt赋能python:Python怎么再加一个的SEO

Python怎么再加一个的SEO 作为一名有10年Python编程经验的工程师&#xff0c;我深知如何将Python项目优化为搜索引擎友好的代码。当谈到SEO时&#xff0c;构建优化的代码比任何其他技术都更加重要。在本文中&#xff0c;我将介绍一些Python中的关键SEO技巧&#xff0c;并强调如…

Spring 事务管理方案和事务管理器及事务控制的API

目录 一、事务管理方案 1. 修改业务层代码 2. 测试 二、事务管理器 1. 简介 2. 在配置文件中引入约束 3. 进行事务配置 三、事务控制的API 1. PlatformTransactionManager接口 2. TransactionDefinition接口 3. TransactionStatus接口 往期专栏&文章相关导读 …

【前端 - CSS】第 11 课 - 选择器

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、基础选择器 2.1、标签选择器 2.2、类选择器 2.3、id 选择器 2.4、通配符选择器 3、画盒子 4、总结 1、缘…

chatgpt赋能python:如何使用Python删除变量中的数据?

如何使用Python删除变量中的数据&#xff1f; Python是一种非常流行的编程语言&#xff0c;许多开发人员使用它来开发高效和可靠的应用程序。在处理数据时&#xff0c;Python提供了一些内置功能来执行一些基本任务。本文将探讨如何使用Python删除变量中的数据的方法。 什么是…

差动保护原理

差动保护是输入的两端CT矢量差&#xff0c;当达到设定的动作值时启动动作元件。保护范围在输入的两端CT之间的设备&#xff08;可以是线路&#xff0c;发电机&#xff0c;电动机&#xff0c;变压器等电气设备&#xff09; 什么是差动保护 电流差动保护是中的一种保护。正相序是…

UDP协议和TCP协议

目录 UDP TCP 通过序列号与确认应答提高可靠性 为什么TCP是三次握手 为什么是四次挥手 超时重传机制 流控制 利用窗口控制提高速度 窗口控制与重发控制 拥塞控制 延迟确认应答 捎带应答 UDP UDP是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。…

总结一下js的浅拷贝和深拷贝

js中对象的赋值是通过将一个对象的引用赋值给另一个变量&#xff0c;两个变量指向同一个内存地址。这意味着如果更改其中一个对象的值&#xff0c;另一个对象的值也会更改。 浅拷贝是将一个对象的值复制给另一个对象&#xff0c;但如果对象中包含对其他对象的引用&#xff0c;…

Linux虚拟网络设备---之Veth pair详解

本文目录 1、我们可以用以下命令来创建veth pair: veth0----veth12、创建二个命名空间namespaces后&#xff0c;可以用以下命令将二个veth设备分别移入二个命名空间ns0和ns1&#xff0c;并将它们连接起来。12、或者用以下命令在创建namespaces后&#xff0c;直接在二个namespac…

设备树的引入及简明教程

首先说明&#xff0c;设备树不可能用来写驱动。 设备树只是用来给内核里的驱动程序&#xff0c;指定硬件的信息。比如LED驱动&#xff0c;在内核的驱动程序里去操作寄存器&#xff0c;但是操作哪一个引脚&#xff1f;这由设备树指定。 需要编写设备树文件(dts: device tree s…

【协议】NVMe over RoCE |nvmeof

什么是nvme nvme ssd和普通ssd区别 ssd是固态硬盘&#xff0c;普通的ssd配的是SATA口&#xff08;AHCI协议&#xff09;&#xff0c;nvme ssd配的是PCIe口&#xff08;nvme传输协议&#xff09; 相比普通SSD的SATA口&#xff0c;nvme的PCIe口有巨大的性能优势。 更多详情见&…

HTTP超详细教程

1&#xff0c;HTTP协议 1.1&#xff0c;HTTP简述 HTTP全称为超文本传输协议&#xff0c;是一种应用比较广泛的应用层协议。 那何为超文本&#xff1f; 超文本指的是传输的内容不仅仅是文本&#xff0c;比如 html&#xff0c;css&#xff0c;javaScript 等数据&#xff0c;还…