C++11的lambda表达式

Lambda表达式是一种匿名函数,允许我们在不声明方法的情况下,直接定义函数。它是函数式编程的一种重要特性,常用于简化代码、优化程序结构和增强代码可读性。

lambda表达式的语法非常简单,具体定义如下:

[ captures ] ( params ) specifiers exception -> ret { body }

举例:

#include <iostream>

int main()
{
	int x = 3, y = 4;

	auto res = [x](int y)->int{
		return x + y;
	};

	std::cout << res(y) << std::endl;

	system("pause");
	return 0;
}
  • [ captures ] —— 捕获列表,它可以捕获当前函数作用域的零个或多个变量,变量之间用逗号分隔。在对应的例子中,[x]是一个捕获列表,不过它只捕获了当前函数作用域的一个变量x,在捕获了变量之后,我们可以在lambda表达式函数体内使用这个变量,比如return x * y。另外,捕获列表的捕获方式有两种:按值捕获和引用捕获
  • ( params ) —— 可选参数列表,语法和普通函数的参数列表一样,在不需要参数的时候可以忽略参数列表。对应例子中的(int y)。
  • specifiers —— 可选限定符,C++11中可以用mutable,它允许我们在lambda表达式函数体内改变按值捕获的变量,或者调用非const的成员函数。上面的例子中没有使用说明符。
  • exception —— 可选异常说明符,我们可以使用noexcept来指明lambda是否会抛出异常。对应的例子中没有使用异常说明符。
  • ret —— 可选返回值类型,不同于普通函数,lambda表达式使用返回类型后置的语法来表示返回类型,如果没有返回值(void类型),可以忽略包括->在内的整个部分。另外,我们也可以在有返回值的情况下不指定返回类型,这时编译器会为我们推导出一个返回类型。对应到上面的例子是->int。
  • { body } —— lambda表达式的函数体,这个部分和普通函数的函数体一样。对应例子中的{ return x + y; }。

在lambda函数的定义中,参数列表返回类型都是可选的部分,而捕捉列表函数体都可能为空。那么在极端情况下,C++11中最为简略的lambda函数只需要声明为

  []{};

就可以了。不过理所应当地,该lambda函数不能做任何事情。

捕获列表的作用域

捕获列表中的变量存在于两个作用域——lambda表达式定义的函数作用域以及lambda表达式函数体的作用域。

前者是为了捕获变量,后者是为了使用变量。另外,标准还规定能捕获的变量必须是一个自动存储类型。简单来说就是非静态的局部变量。

在这里插入图片描述可以看到,当我们把全局变量和一个静态变量放到捕获列表中会报错。

如果我们要在函数体内使用全局变量和静态变量的话直接使用即可。

#include <iostream>

int x = 0;

int main()
{
	static int y = 2;

	auto res = []()->int{
		return x + y;
	};

	std::cout << res() << std::endl;

	system("pause");
	return 0;
}

如果我们将一个lambda表达式定义在全局作用域,那么lambda表达式的捕获列表必须为空。因为根据上面提到的规则,捕获列表的变量必须是一个自动存储类型,但是全局作用域并没有这样的类型,比如:

#include <iostream>

int x = 0;

static int y = 2;

auto res = []()->int {
	return x + y;
};

int main()
{
	std::cout << res() << std::endl;

	system("pause");
	return 0;
}

捕获列表的捕获值和引用

捕获列表的捕获方式分为捕获值和捕获引用。

捕获值

#include <iostream>

int main()
{
	int x = 3, y = 2;

	auto res = [x, y]()->int {
		return x + y;
	};

	std::cout << res() << std::endl;	// 5

	system("pause");
	return 0;
}

捕获引用

捕获引用的语法与捕获值只有一个&的区别,要表达捕获引用我们只需要在捕获变量之前加上&,类似于取变量指针。只不过这里捕获的是引用而不是指针,在lambda表达式内可以直接使用变量名访问变量而不需解引用

#include <iostream>

int main()
{
	int x = 3, y = 2;

	auto res = [&x, &y]()->int {
		return x + y;
	};

	std::cout << res() << std::endl;

	system("pause");
	return 0;
}

如果只是读捕获列表中的值,那么上述两个例子没有区别,但是要修改捕获列表的值就有区别。

void bar1()
{
    int x = 5, y = 8;
    auto foo = [x, y] {
        x += 1;             // 编译失败,无法改变捕获变量的值
        y += 2;             // 编译失败,无法改变捕获变量的值
        return x * y;
    };
    std::cout << foo() << std::endl;
}

void bar2()
{
    int x = 5, y = 8;
    auto foo = [&x, &y] {
        x += 1;
        y += 2;
        return x * y;
    };
    std::cout << foo() << std::endl;
}

在上面的代码中函数bar1无法通过编译,原因是我们无法改变捕获变量的值。这就引出了lambda表达式的一个特性:捕获的变量默认为常量,或者说lambda是一个常量函数(类似于常量成员函数)。

bar2函数里的lambda表达式能够顺利地通过编译,虽然其函数体内也有改变变量x和y的行为。这是因为捕获的变量默认为常量指的是变量本身,当变量按值捕获的时候,变量本身就是值,所以改变值就会发生错误。相反,在捕获引用的情况下,捕获变量实际上是一个引用,我们在函数体内改变的并不是引用本身,而是引用的值,所以并没有被编译器拒绝。

另外,使用mutable说明符可以移除lambda函数的常量性。例如:

void bar1()
{
	int x = 5, y = 8;
	auto foo = [x, y]() mutable{
		x += 1;             
		y += 2;             
		return x * y;
	};
	std::cout << foo() << std::endl;
}

但是这并不意味着二者就没有任何区别了,捕获值和捕获引用最根本上和函数参数的值传递和引用传递是一样的。例如:

void bar1()
{
	int x = 5, y = 8;
	auto foo = [x, y]() mutable{
		x += 1;             // 编译失败,无法改变捕获变量的值
		y += 2;             // 编译失败,无法改变捕获变量的值
		return x * y;
	};

	std::cout << foo() << std::endl;	// 60
	std::cout << x << std::endl;		// 5
	std::cout << y << std::endl;		// 8
}

void bar2()
{
	int x = 5, y = 8;
	auto foo = [&x, &y] {
		x += 1;
		y += 2;
		return x * y;
	};

	std::cout << foo() << std::endl;	// 60
	std::cout << x << std::endl;		// 6
	std::cout << y << std::endl;		// 10
}

对于捕获值的lambda表达式还有一点需要注意,捕获值的变量在定义lambda表达式时就固定下来,无论在定义lambda表达式定义之后如何修改这个变量的值,在lambda函数体内都不会改变。例如:

void bar2()
{
	int x = 5, y = 8;
	auto foo = [x, &y] {
		return x * y;
	};

	x += 1;
	y += 2;

	std::cout << foo() << std::endl;	// 50
	std::cout << x << std::endl;		// 6
	std::cout << y << std::endl;		// 10
}

特殊的捕获方法

lambda表达式的捕获列表除了指定捕获变量之外还有3种特殊的捕获方法。

1.[this] —— 捕获this指针,捕获this指针可以让我们使用this类型的成员变量和函数。

class A
{
public:

	void Print()
	{
		std::cout << "class A" << std::endl;
	}

	void Test()
	{
		auto foo = [this] {
			
			Print();

			x += 2;

			std::cout << x << std::endl;
		};

		foo();
	}

private:

	int x = 2;
};

2.[=] —— 捕获lambda表达式定义作用域的全部变量的值,包括this。

void bar2()
{
	int x = 5, y = 8;
	auto foo = [=] {
		return x * y;
	};

	std::cout << foo() << std::endl;	// 40
}

3.[&] —— 捕获lambda表达式定义作用域的全部变量的引用,包括this。

void bar2()
{
	int x = 5, y = 8;
	auto foo = [&] {
		return x * y;
	};

	std::cout << foo() << std::endl;	// 40
}

无状态的lambda表达式

C++标准对于无状态的lambda表达式有着特殊的照顾,即它可以隐式转换为函数指针,例如:

void f(void(*)()) {}
void g() { f([] {}); } // 编译成功

在上面的代码中,lambda表达式[] {}隐式转换为void(*)()类型的函数指针。同样,看下面的代码:

void f(void(&)()) {}
void g() { f(*[] {}); }

这段代码也可以顺利地通过编译。我们经常会在STL的代码中遇到lambda表达式的这种应用。

在STL中使用lambda表达式

使用lambda表达式作为STL算法的第三个参数

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
	std::vector<int> x = { 1,2,3,4,5,6 };

	std::cout << *std::find_if(x.begin(), x.end(), [](int i) {

		return (i % 3) == 0;

	}) << std::endl;

	system("pause");
	return 0;
}

这段代码使用了std::find_if算法来搜索满足指定条件的元素,并输出该元素的值。

首先,创建了一个名为x的std::vector,包含了整数1到6的元素。

然后,调用std::find_if算法,传入参数x.begin()和x.end(),用于指定搜索范围为整个容器x。

在这里,使用了Lambda表达式作为第三个参数,对每个元素进行判断。Lambda表达式[](int i) { return (i % 3) == 0; }的作用是判断一个整数是否能被3整除,如果能则返回true,否则返回false。Lambda表达式的参数为当前迭代到的元素,这里表示为i。

std::find_if算法会依次遍历容器中的元素,并将每个元素传入Lambda表达式中进行判断,直到找到第一个满足条件的元素或遍历完整个容器。一旦找到满足条件的元素,std::find_if算法会返回指向该元素的迭代器。

最后,通过*运算符取出迭代器指向的元素值,并使用std::cout输出到标准输出流中。

因此,这段代码输出的结果是容器x中能被3整除的第一个元素的值,即3。

Lambda 表达式具有以下优点:

  1. 简洁:Lambda 表达式可以简洁地定义和使用,省去了传统函数的繁琐定义过程。

  2. 表达力强:Lambda 表达式可以更直观地表达函数的意图,特别适用于一些简单的函数功能。

  3. 更好的可读性:Lambda 表达式的内联定义使得代码更紧凑,更易于理解和维护。

  4. 捕获外部变量:Lambda 表达式可以捕获外部作用域的变量,使得在函数体内可以访问这些变量,方便在函数内部进行计算和操作。

然而,Lambda 表达式也有一些缺点:

  1. 可读性可能有限:过于复杂或嵌套的 Lambda 表达式可能会降低代码的可读性,特别是当 Lambda 表达式较长时。

  2. 不易重用:Lambda 表达式通常用于一次性的函数功能,不太适用于需要重复使用的情况。对于需要在多个地方使用的函数,使用具名的函数对象或函数指针更具可重用性。

  3. 难以调试:由于是匿名函数,Lambda 表达式的调试可能相对困难,特别是在复杂的代码场景中。

综上所述,Lambda 表达式在简洁、表达力等方面具有一些优点,但也需谨慎使用,根据具体情况来判断是否适合使用 Lambda 表达式。

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

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

相关文章

React学习计划-React16--React基础(七)redux使用与介绍

笔记gitee地址 一、redux是什么 redux是一个专门用于做状态管理的js库&#xff08;不是react插件库&#xff09;它可以用在react、angular、vue的项目中&#xff0c;但基本与react配合使用作用&#xff1a;集中式管理react应用中多个组件共享的状态 二、什么情况下需要使用r…

antv/x6_2.0学习使用(三、内置节点和自定义节点)

内置节点和自定义节点 一、节点渲染方式 X6 是基于 SVG 的渲染引擎&#xff0c;可以使用不同的 SVG 元素渲染节点和边&#xff0c;非常适合节点内容比较简单的场景。面对复杂的节点&#xff0c; SVG 中有一个特殊的 foreignObject 元素&#xff0c;在该元素中可以内嵌任何 XH…

数据结构与算法笔记

数据结构&#xff1a; 就是指一组数据的存储结构 算法&#xff1a; 就是操作数据的一组方法 数据结构和算法 两者关系 数据结构和算法是相辅相成的。数据结构是为算法服务的&#xff0c;算法要作用在特定的数据结构之上。 数据结构是静态的&#xff0c;它只是组织数据的一…

Windows窗口程序详解

今天来给大家详细剖析一下Windows的消息机制 一、什么是消息机制 首先消息机制是Windows上面进程之间通信的一种方式&#xff0c;除此之外还包括共享内存&#xff0c;管道&#xff0c;socket等等进程之间的通信方式&#xff0c;当然socket还可以实现远程进程之间的通信&#…

JS变量和函数提升

JS变量和函数提升 JS变量提升编译阶段执行阶段相同变量或函数 变量提升带来的问题变量容易不被察觉的遭覆盖本应销毁的变量未被销毁 如何解决变量提升带来的问题 JS变量提升 sayHi()console.log(myname)var myname yyfunction sayHi() {console.log(Hi) }// 执行结果: // Hi …

我的2023年,平淡中寻找乐趣

文章目录 两个满意我学会了自由泳。学习英语 一个较满意写博客 2024的期望 2023年&#xff0c;我有两个满意&#xff0c;一个较满意。 两个满意 我学会了自由泳。 开始练习自由泳是从2023年3月份&#xff0c;我并没有请教练&#xff0c;而是自己摸索。在抖音上看自由泳的视频…

【一分钟】ThinkPHP v6.0 (poc-yaml-thinkphp-v6-file-write)环境复现及poc解析

写在前面 一分钟表示是非常短的文章&#xff0c;只会做简单的描述。旨在用较短的时间获取有用的信息 环境下载 官方环境下载器&#xff1a;https://getcomposer.org/Composer-Setup.exe 下载文档时可以设置代理&#xff0c;不然下载不上&#xff0c;你懂的 下载成功 cmd cd…

JavaWeb——前端之HTMLCSS

学习视频链接&#xff1a;https://www.bilibili.com/video/BV1m84y1w7Tb/?spm_id_from333.999.0.0 一、Web开发 1. 概述 能通过浏览器访问的网站 2. Web网站的开发模式——主流是前后端分离 二、前端Web开发 1. 初识 前端编写的代码通过浏览器进行解析和渲染得到我们看到…

Java多线程常见的成员方法(线程优先级,守护线程,礼让/插入线程)

目录 1.多线程常见的成员方法2.优先级相关的方法3.守护线程&#xff08;备胎线程&#xff09;4.其他线程 1.多线程常见的成员方法 ①如果没有给线程设置名字&#xff0c;线程是有默认名字 的&#xff1a;Thread-X(X序号&#xff0c;从0开始) ②如果要给线程设置名字&#xff0c…

【SAM系列】Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation

论文链接&#xff1a;https://arxiv.org/pdf/2308.14936.pdf 核心&#xff1a; finetune SAM,为了不依赖外部prompt&#xff0c;通过将深层的特征经过一个编-解码器来得到prompt embedding&#xff1b;finetune完之后做蒸馏

苯酚,市场预计将以5%左右的复合年增长率

苯酚是一种重要的化合物&#xff0c;用于广泛的工业应用&#xff0c;包括塑料、树脂和合成纤维的生产。在建筑、汽车和电子行业不断增长的需求推动下&#xff0c;苯酚市场在过去十年中经历了稳步增长。全球苯酚市场分析&#xff1a; 在 2021-2026 年的预测期内&#xff0c;全球…

Java并发编程(三)

并发编程的三个特性 并发编程的三个重要特性是原子性、可见性和有序性。 原子性&#xff1a;原子性指的是一个操作是不可中断的&#xff0c;要么全部执行成功&#xff0c;要么全部不执行&#xff0c;是不可再分割的最小操作单位。保证原子性可以避免多个线程同时对共享数据进行…

《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三

Lecture 07 Machine Level Programming III Procedures 机器级别的程序三 文章目录 Lecture 07 Machine Level Programming III Procedures 机器级别的程序三概述程序机制 栈结构栈说明栈定义推入数据弹出数据 调用控制代码示例程序控制流程%rip 传递数据ABI 标准示例 管理局部…

WPF Button使用漂亮 控件模板ControlTemplate 按钮使用控制模板实例及源代码 设计一个具有圆角边框、鼠标悬停时颜色变化的按钮模板

续前两篇模板文章 模板介绍1 模板介绍2 WPF中的Button控件默认样式简洁&#xff0c;但可以通过设置模板来实现更丰富的视觉效果和交互体验。按钮模板主要包括背景、边框、内容&#xff08;通常为文本或图像&#xff09;等元素。通过自定义模板&#xff0c;我们可以改…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

Docker自建私人云盘系统

Docker自建私人云盘系统。 有个人云盘需求的人&#xff0c;主要需求有这几类&#xff1a; 文件同步、分享需要。 照片、视频同步需要&#xff0c;尤其是全家人都是用的同步。 影视观看需要&#xff08;分为家庭内部、家庭外部&#xff09; 搭建个人网站/博客 云端OFFICE需…

TiDB在WMS物流系统的应用与实践 (转)

业务背景 北京科捷物流有限公司于2003年在北京正式成立,是ISO质量管理体系认证企业、国家AAAAA级物流企业、海关AEO高级认证企业,注册资金1亿元,是中国领先的大数据科技公司——神州控股的全资子公司。科捷物流融合B2B和B2C的客户需求,基于遍布全国的物流网络与自主知识产…

电话号码的字母组合[中等]

一、题目 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意1不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&am…

【MySQL学习笔记008】多表查询及案例实战

1、多表关系 概述&#xff1a;项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上可分为三种&a…

什么是 NLP (自然语言处理)

NLP&#xff08;自然语言处理&#xff09;到底是做什么&#xff1f; NLP 的全称是 Natural Language Processing&#xff0c;翻译成中文称作&#xff1a;自然语言处理。它是计算机和人工智能的一个重要领域。顾名思义&#xff0c;该领域研究如何处理自然语言。 自然语言就是我…
最新文章