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

目录

一、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;
}

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

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

相关文章

ROS话题通信基本操作(python)

目录 一、发布 1、实现步骤 2、代码实例 二、接收 1、实现步骤 2、代码实例 三、配置运行 1、修改CMakeLists.txt 2、修改可执行权限 3、运行结果 一、发布 1、实现步骤 1.导包 2.初始化 ROS 节点:命名(唯一) 3.实例化 发布者 对象 4.组织被发布的数据&#xff0c;…

浅谈Django之单元测试

一、什么是单元测试 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。如果测试通过则说明我们这个函数或功能能够正常工作&#xff0c;如果失败要么测试用例不正确&#xff0c;要么函数有bug需要修复。 二、如何使用单元测试 from django.test imp…

Spring Cloud + Vue前后端分离-第2章 使用Maven搭建SpringCloud项目

第2章 使用Maven搭建SpringCloud项目 Maven两大核心功能&#xff1a; 依赖管理&#xff08;Jar包管理&#xff09; 构建项目&#xff08;项目打包&#xff09; 使用Eureka搭建注册中心 使用spring initializr创建spring cloud项目 SpringCloud和Maven简介 SpringBoot和Spr…

[ISCTF 2023]——Web、Misc较全详细Writeup、Re、Crypto部分Writeup

前言 由于懒我直接把上交的wp稍加修改拉上来了&#xff0c;凑活看 文章目录 前言Pwntest_ncnc_shell ReverseCreakmeEasyRebabyReeasy_z3mix_reeasy_flower_tea Webwhere_is_the_flag圣杯战争!!!绕进你的心里easy_websitewafrez_ini1z_Ssqldouble_picklewebincludefuzz!恐怖G…

全网最新最全的自动化测试:python+pytest接口自动化-接口测试基础

接口定义 一般我们所说的接口即API&#xff0c;那什么又是API呢&#xff0c;百度给的定义如下&#xff1a; API&#xff08;Application Programming Interface&#xff0c;应用程序接口&#xff09;是一些预先定义的接口&#xff08;如函数、HTTP接口&#xff09;&#xff0c…

从0到1的跨境电商创业经验分享!个人如何做跨境电商创业?

近年来&#xff0c;跨境电商成为了一种非常流行的创业方式&#xff0c;都知道国内贸易不好做&#xff0c;许多卖家都想通过跨境电商创业&#xff0c;但他们不知道具体的过程&#xff0c;今天龙哥我就分享一下我自己在跨境电商创业总结出来的经验&#xff0c;帮助你在跨境电商领…

【powerjob】定时任务调度器 xxl-job和powerjob对比

文章目录 同类产品对比资源及部署相关资源占用对比&#xff1a;部署方式&#xff1a;xxl job :调度器&#xff1a;执行器&#xff1a; powerjob&#xff1a;调度器&#xff1a;执行器&#xff1a; 总结 背景&#xff1a; 目前系统的定时任务主要通过Spring框架自带的Scheduled注…

buuctf [极客大挑战 2019]Havefun1

解题思路&#xff1a; 小习惯 本题先看看源码或者检查一下&#xff0c;可能这是俺的一个小习惯。 源码里面都看到了php的代码 php代码解析&#xff1a; $cat$_GET[cat]; echo $cat; if($catdog){ echo Syc{cat_cat_cat_cat}; } 1.$ca…

新手村之SQL——分组与子查询

1.GROUP BY GROUP BY 函数就是 SQL 中用来实现分组的函数&#xff0c;其用于结合聚合函数&#xff0c;能根据给定数据列的每个成员对查询结果进行分组统计&#xff0c;最终得到一个分组汇总表。 mysql> SELECT country, COUNT(country) AS teacher_count-> FROM teacher…

T-SQL的多表查询

前面讲述过的所有查询都是基于单个数据库表的查询。如果一个查询需要对多个表进行操作&#xff0c;就称为联接查询&#xff0c;联接查询的结果集或结果称为表之间的联接。 联接查询实际上是通过各个表之间共同列的关联性来查询数据的&#xff0c;它是关系数据库查询最主要的特征…

听GPT 讲Rust源代码--src/tools(7)

File: rust/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs 在Rust源代码中&#xff0c;rust-analyzer/crates/ide/src/inlay_hints/chaining.rs这个文件的作用是生成Rust代码中的链式调用提示。 具体来说&#xff0c;当我们使用链式调用时&#xff0c;例如A…

C语言——深入理解指针(5)

目录 1. sizeof和strlen的对比 1.1 sizeof 1.2 strlen 1.3 sizeof和strlen 的对比 2. 数据和指针题解析 2.1 一维数组 2.2 字符数组 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2,6 2.3 二维数组 3. 指针运算题解析 3.1 例1 3.2 例2 3.3 例3 3.4 例4 3.5 例5 3.6 例…

python中的进制转换和原码,反码,补码

python中的进制转换和原码,反码,补码 计算机文件大小单位 b bit 位(比特) B Byte 字节 1Byte 8 bit #一个字节等于8位 可以简写成 1B 8b 1KB 1024B 1MB 1024KB 1GB 1024MB 1TB 1024GB 1PB 1024TB 1EB 1024PB 进制分类 二进制:由2个数字组成,有0 和 1 pyth…

如何无线桥接路由器,让你的网络覆盖范围变大,做到网络信号无缝连接

你是否希望通过在两个路由器之间创建无线网桥(网络桥接)来扩大网络覆盖范围?好吧,你来对地方了!在当今日益互联的世界,拥有一个强大可靠的网络比以往任何时候都更重要。 无线网桥允许你无线连接两个或多个路由器,有效地扩展网络覆盖范围,并在更大的区域提供无缝的互联…

巧借C++算法实现冒泡排序算法

目录 引言冒泡排序原理具体实现步骤示例代码时间复杂度和稳定性优化可能性结束语 引言 作为计算机专业出身的开发者&#xff0c;以及从事软件开发相关的小伙伴&#xff0c;想必对C语言并不陌生&#xff0c;它是一门非常厉害的编程语言&#xff0c;不仅是基于程序底层的语言&a…

Proteus8.16仿真软件安装图文教程(Proteus 8 Professional)

Proteus8.16 &#x1f527;软件安装包下载链接&#xff1a;&#x1f527;视频教程&#x1f527;1 安装软件解压&#x1f527;2 安装&#x1f527;3 破解&#x1f527;4 汉化 &#x1f527;软件安装包下载链接&#xff1a; Proteus8.16软件下载链接 1、本文关于Proteus8.16 SP…

关于“你对SpringCloud的理解”

看看普通人和高手是如何回答这个问题的&#xff1f; 普通人 Spring Cloud 是一套微服务解决方案 它包括配置中心、RPC 通信、服务注册、服务熔断等组件 高手 Spring Cloud 是一套 分布式微服务的技术解决方案 它提供了快速构建分布式系统的 常用的一些组件 比如说配置…

人工智能与供应链行业融合:开启智能化供应链的新时代

随着人工智能技术的快速发展&#xff0c;供应链行业正迎来革命性变革。本文将探索人工智能在供应链管理中的应用领域&#xff0c;并分析其带来的益处和挑战&#xff0c;展望人工智能与供应链融合的未来发展趋势。 引言 供应链管理是企业运营中不可或缺的重要组成部分。它涵盖了…

Typora免费版安装教程(仅供学习)

目录 一、Typora简介二、Typora安装三、Typora补丁四、Typora使用体验五、总结 一、Typora简介 Typora是一款非常流行的Markdown编辑器&#xff0c;它能够将Markdown文本转化为漂亮的排版&#xff0c;并且支持实时预览。Typora具有简单易用的界面&#xff0c;使得用户可以轻松地…

微机原理9

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