C++进阶篇6---lambda表达式

news2024/11/18 12:20:29

目录

一、lambda表达式

1.引入 

2、lambda表达式语法

二、包装器---function

1.引入

2.包装器介绍

三、bind


一、lambda表达式

1.引入 

class Person {
public:
	Person(int age,string name)
		:_age(age)
		,_name(name)
	{}
//private://方便后面的举例
	int _age;
	string _name;
};

int main()
{
	vector<Person>v = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };
	//当我们要通过姓名/年龄排序时,我们应该怎么办?
	//sort(v.begin(),v.end());
	return 0;
}

根据我们之前学过的知识,我们可以写两个仿函数,分别对应姓名和年龄的比较,如下

struct comp_age {
	bool operator()(const Person& x, const Person& y) {
		return x._age < y._age;
	}
};

struct comp_name {
	bool operator()(const Person& x, const Person& y) {
		return x._name < y._name;
	}
};

但这种方法太麻烦了,而且一旦排序的标准多起来,给仿函数起什么名字都是个问题,所以出了lambada表达式,如下

int main()
{
	vector<Person>v = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };
	sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._age < y._age; });
	sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._name < y._name; });
	return 0;
}

可以看出lambda表达式实际上一个匿名函数,跟函数很相似

2、lambda表达式语法

lambda表达式书写格式:

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

lambda表达式各部分说明

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

注意:

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

其实就是看着比较复杂,跟一般的函数比起来,就只是少了一个函数名,多了一个捕捉列表而已,所以只要把捕捉列表的功能能清楚就能很好的掌握lambda表达式

捕获列表说明

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

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
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函数,除了b是引用捕捉,其他全是传值方式捕捉
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    //这里提醒一下:lambda表达式只能捕捉在它上面定义的变量,在它之后定义的无法捕捉

    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}

注意:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割

比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误

比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
    f1();
    f2();
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
    //f1 = f2;   // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}

 规则其实并不复杂,就是细节比较多,主要是没咋见识过,用多了就会发现它真的很香

这是上面代码的反汇编调试信息,其实lambda表达式的底层实现就是仿函数,只是类型名很奇怪,但都是调用的operator()这个成员函数

 注意:一般lambda表达式的类型名都是class lambda_XXXXX这种样式的,采用的是lambda_uuid的编码方式,不同编译器的命名方式不同,但是我们也能看出它是一个类的函数调用

对比仿函数

我们就能进一步理解捕捉列表,它其实和仿函数的成员变量很相似,一个是自己初始化,一个是捕捉已经存在的

二、包装器---function

1.引入

截止到目前,我们已经学了很3种"函数"---普通函数、仿函数、lambda表达式,它们的类型各不相同,一旦有模板需要传函数,就会导致一个问题,传不同类型的"函数",会产生多个实例,如下
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

而这一切都是因为类型不同,所以我们需要将它们的类型进行统一包装,避免这种情况发生,所以出了function包装器

2.包装器介绍

std::function 在头文件 < functional >
// 类模板 原型如下
template < class T > function ;     // undefined
template < class Ret , class ... Args >
class function < Ret ( Args ...) > ;
模板参数说明:
  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

function类型的对象能用函数指针,lambda表达式,仿函数赋值,前提是参数及返回值和function类型一样,如下

int main()
{
	// 函数名
	function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	cout << typeid(func1).name() << endl;
	 //函数对象
	function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	cout << typeid(func2).name() << endl;

	 //lamber表达式
	function<double(double)> func3 = [](double d)->double { return d / 4; };
	cout << useF(func3, 11.11) << endl;
	cout << typeid(func3).name() << endl;

	return 0;
}

可以看出模板只实例化了一份,有点类似多态,传不同的函数,会有不同的效果,而这都是因为function包装器的类型是统一的

有人可能觉得这个用处好像不是很大,但其实在大型项目中,还是很有必要的,而且它的应用场景远不止于此,我们来看看下面的应用场景

正常来说,我们得写if-else语句或者switch语句一个符号一个符号的匹配 

但是现在我们可以用包装器function来简化代码,使得它看起来更加优雅

3.类成员函数的包装

class Plus {
public:
	int ADDi(int x, int y)
	{
		return x, y;
	}
	static double ADDd(double x, double y)
	{
		return x + y;
	}
private:
	int a;
};

int main()
{
//1.注意类的成员函数的地址怎么取,&域名::函数名
//2.非静态成员函数,第一个参数可以是类,也可以是指针
	function<int(Plus, int, int)>func1 = &Plus::ADDi;//可以是Plus,也可以是Plus*
	function<int(Plus*, int, int)>func2 = &Plus::ADDi;
	function<double(double, double)>func3 = &Plus::ADDd;
	return 0;
}

 

三、bind

std::bind函数定义在头文件中,是一个 函数模板 ,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

看着概念很复杂,我们来写几个看看,就大致明白了

int test(int x, int y)
{
	return x * 2 + y * 3;
}

int main()
{
    //placeholders::_xx代表的是funcion中的参数,_1代表第一个,_2代表第二个以此类推
	function<int(int)>fun1 = bind(test, 2, placeholders::_1);
	cout << fun1(1) << endl;
	function<int(int)>fun2 = bind(test, placeholders::_1, 2);
	cout << fun2(1) << endl;

	function<int(int,int)>fun3 = bind(test, placeholders::_2, placeholders::_1);
	cout << fun3(2, 3) << endl;

	return 0;
}

 

这里解释一下,这三个打印结果,传参的对应关系如下

很显然,placeholders::_1,_2,…… 对应的是function中写的参数顺序,bind中第一个参数填函数名(当然仿函数对象,lambda表达式都可以),后面的参数分别对应函数参数的顺序。

function包装器的参数可以少于函数的参数,但是bind中传的参数一般要和原函数参数个数对应,我们可以通过bind来固定一些默认的参数值,或者调换一下参数的顺序,让我们用起函数来更加的"舒服"

成员函数的bind

class Plus {
public:
	int ADDi(int x, int y)
	{
		return x, y;
	}
	static double ADDd(double x, double y)
	{
		return x + y;
	}
private:
	int a = 0;
};

int main()
{
    //如果只是单纯想想使用该函数的功能,可以将第一个参数写死,虽然第一个参数是指针,这里也可以传对象,可以看作是特例
	function<int(int, int)>func1 = bind(&Plus::ADDi, Plus(), placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;
	//这种是不行的,右值不能被取地址!!!
	//function<int(int, int)>func2 = bind(&Plus::ADDi, &Plus(), placeholders::_1, placeholders::_2);
	//cout << func2(1, 2) << endl;

	function<double(double, double)>func3 = bind(&Plus::ADDd, placeholders::_1, placeholders::_2);
	cout << func3(1.1, 2.3) << endl;
	return 0;
}

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

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

相关文章

全网最新最全的自动化测试教程:python+pytest接口自动化(9)-cookie绕过登录(保持登录状态

在编写接口自动化测试用例或其他脚本的过程中&#xff0c;经常会遇到需要绕过用户名/密码或验证码登录&#xff0c;去请求接口的情况&#xff0c;一是因为有时验证码会比较复杂&#xff0c;比如有些图形验证码&#xff0c;难以通过接口的方式去处理&#xff1b;再者&#xff0c…

【openssl】RSA 生成公钥私钥 |通过私钥获取公钥

通过博客&#xff1a;Window系统如何编译openssl 编译出openssl.exe&#xff08;位于apps文件夹下&#xff09;。 现在需要使用它获得公钥私钥、通过私钥获取公钥 目录 说明&#xff01;&#xff01;&#xff01; 一.定位openssl.exe目录 二、进入命令cmd 三、生成私钥 …

知虾数据工具:助力Shopee商家实现数据化运营的有力助手

在如今竞争激烈的电商市场中&#xff0c;了解市场趋势、优化产品和店铺运营、了解竞争对手等是商家取得成功的关键。而针对Shopee平台的知虾数据工具则为商家和市场分析师提供了一个强大的数据分析工具。本文将介绍知虾数据工具的主要功能&#xff0c;包括多站点数据分析、行业…

抖音视频水印怎么去除?这三个视频去水印技巧值得收藏!

抖音视频水印怎么去除&#xff1f;随着互联网的持续发展&#xff0c;越来越多的人选择使用视频分享平台来展示他们的生活与工作。然而&#xff0c;上传到这些平台上的许多视频常常遭到恶意水印的攻击&#xff0c;严重影响了观众的观看体验。今天&#xff0c;我们将分享三个视频…

一键式紧急报警柱系统

随着科技的不断发展&#xff0c;一键式紧急报警柱在我们的生活和工作中扮演着越来越重要的角色。在这篇文章中&#xff0c;我们将一起探究与一键式紧急报警柱有关的知识。 一键式紧急报警柱是一种常见的安全防护设备&#xff0c;能够在紧急情况下快速发出警报&#xff0c;保护…

ChatGPT发布一年后,搜索引擎的日子还好吗?

导读&#xff1a;生成式AI&#xff0c;搜索引擎的终结者还是进化加速器 ChatGPT发布刚刚一年&#xff0c;互联网世界已经换了人间。 2023年&#xff0c;以ChatGPT和大模型为代表的生成式AI浪潮对全球互联网、云计算、人工智能领域都带来巨大冲击。而且生成式AI在各行各业的应用…

Oracle的数据一致性机制原理

一、前言 在单用户环境下&#xff0c;在操作数据库是不需要考虑其他用户会修改同一个数据。但是在多用户的情况下&#xff0c;多个事务可能会修改同一个数据&#xff0c;最终会得到错误的数据结果。 Oracle数据库是通过 multiversion consistency model&#xff08;多版本数据…

HDFS客户端及API操作实验

实验二 HDFS客户端及API操作 实验目的&#xff1a; 1.掌握HDFS的客户端操作&#xff0c;包括上传文件、下载文件、重命名、查看目录等&#xff1b; 2.掌握HDFS的Java API使用&#xff0c;能够利用Java API实现上传、下载等常用操作&#xff1b; 实验内容&#xff1a; HDF…

使用coco数据集进行语义分割:数据预处理与损失函数

如何coco数据集进行目标检测的介绍已经有很多了&#xff0c;但是关于语义分割几乎没有。本文旨在说明如何处理 stuff_train2017.json stuff_val2017.json panoptic_train2017.json panoptic_val2017.json&#xff0c;将上面那些json中的dict转化为图片的label mask&am…

【Qt开发流程】之定时器事件与随机数示例

描述 QObject是所有Qt对象的基类&#xff0c;提供了Qt中基础的定时器支持。通过QObject::startTimer()函数&#xff0c;可以使用毫秒为单位的时间间隔来启动一个定时器。该函数返回一个唯一的整数定时器ID。该计时器现在将以规律的间隔触发&#xff0c;直到显式调用QObject::k…

谷歌用AI模型发现220万种新材料,研究能力超越人类!

谷歌旗下的AI研究机构DeepMind在全球顶级学术期刊《Nature》上发布了一篇论文&#xff0c;通过深度学习、计算机视觉、大数据等&#xff0c;开发了一个名为GNoME的图神经网络模型&#xff0c;主要用于材料发现。 研究团队通过GnoME便快速发现了220万个新的材料晶体结构&#x…

JVM==>图解字节码指令

一&#xff0c;原始代码 我们来看一下执行这段代码的具体流程 那执行这段代码中 JVM就会把已经编译好的.class文件加载到内存中&#xff0c;交给CPU运行 1&#xff09;常量池载入运行时常量池 我们发现 10 并没有被存入常量池中&#xff0c; 这是因为short范围以内的数字不会…

微机原理9

一、单项选择题(本大题共15小题,每小题3分、共45分。在每小题给出的四个备选项中,选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 8088 系统的内存最大容量为 16MB. 其地址总线为&#xff08;&#xff09; A. 16 位 B. 20 位 C. 24 位 D. 32 位 2,以CPU为核心…

YITH WooCommerce Social Login跨境电商网站社交登录高级版插件

点击阅读YITH WooCommerce Social Login跨境电商网站社交登录高级版插件原文 YITH WooCommerce Social Login跨境电商网站社交登录高级版插件让您的用户节省时间并通过他们的社交资料之一登录或注册网站。 您如何从中受益&#xff1a; 用户无需填写表格、插入个人数据&#…

【数电笔记】06-码制

目录 说明&#xff1a; 二进制代码 1. 二 - 十进制码 2. 常用二 - 十进制代码表 2.1 例题 可靠性代码 1. 格雷码 2. 奇偶校验码 3. 8421奇偶校验码表 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔记并未记录所有章节&#xff0c;只对个人认…

计算机基础知识64

ForeignKey属性 to&#xff1a;设置要关联的表 related_name&#xff1a; 反向操作时&#xff0c;使用的字段名&#xff0c;用于代替原反向查询时的’表名_set’ related_query_name:反向查询操作时&#xff0c;使用的连接前缀&#xff0c;用于替换表名 to_field:设置要关联的表…

【数据分享】2015-2023年我国区县逐月二手房房价数据(Excel/Shp格式)

房价是一个城市发展程度的重要体现&#xff0c;一个城市的房价越高通常代表这个城市越发达&#xff0c;对于人口的吸引力越大&#xff01;因此&#xff0c;房价数据是我们在各项城市研究中都非常常用的数据&#xff01;之前我们分享过2015-2023年我国地级市逐月房价数据&#x…

关于你对 Zookeeper 的理解

看看普通人和高手是如何回答这个问题的&#xff1f; 普通人 Zookeeper 是一种开放源码的分布式应用程序协调服务 是一个分布式的小文件存储系统 一般对开发者屏蔽分布式应用开发过过程种的底层细节 用来解决分布式集群中应用系统的一致性问题 高手 对于 Zookeeper 的理解…

【ArcGIS Pro微课1000例】0047:深度学习--棕榈树提取全流程

一、创建训练样本 对汤加科洛瓦伊种植园每棵棕榈树的健康状况进行清查和评估,这需要花费大量的时间和劳动力。 为简化此过程,将在 ArcGIS Pro 中使用深度学习模型来识别树木,然后根据植被绿度的测量值计算其健康状况。 第一步是找到显示汤加科洛瓦伊的影像,该影像具有足够…

VQD视频质量诊断服务/图像质量诊断/视频流质量诊断/传统方法与深度学习结合的视频质量诊断

随着平安城市、大安防的发展&#xff0c;监控摄像机数量的不断增加&#xff0c;给监控系统的维护工作带来了新的挑战。如何及时了解前端视频设备的运行情况&#xff0c;发现故障并检测恶意遮挡与破坏的不法行为已成为视频监控系统运行的首要迫切问题。对于成千上万个监控摄像机…