【C++11】包装器和bind

文章目录

  • 一. 为什么要有包装器?
  • 二. 什么是包装器?
  • 三. 包装器的使用
  • 四. bind 函数模板
    • 1. 为什么要有 bind ?
    • 2. 什么是 bind ?
    • 3. bind 的使用场景

一. 为什么要有包装器?

function 包装器,也叫作适配器。C++ 中的 function 本质上是一个类模板,也是一个包装器。接下来我们来看看,C++ 为什么引入 function 呢?

在 C++ 中,可调用对象有以下三种:函数名/函数指针、仿函数对象、lambda 表达式

// 加法普通函数
int NormalPlus(int num1, int num2)
{
	return num1 + num2;
}

// 加法仿函数
class FunctorPlus
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 加法 lambda 表达式
	auto LambdaPlus = [](int num1, int num2) {return num1 + num2; };

	// 1、调用普通函数
	NormalPlus(10, 20);
	// 2、调用仿函数对象
	FunctorPlus obj;
	obj(10, 20);
	// 3、调用 lambda 表达式
	LambdaPlus(10, 20);
}

可以看到,它们的调用方式可以不能说相似,只能说是一模一样,且它们函数体中执行的内容都是完全相同的;但是它们彼此之间的类型不同,不能进行相互赋值等操作。
在这里插入图片描述

上面是三种不同的可调用对象,那么它们的类型不同还可以理解;但是在 lambda 表达式中,就算功能相似的 lambda 表达式,它们直接的类型也互不相同:
在这里插入图片描述

这就导致在很多应用场景中,lambda 表达式使用起来是非常不便的。比如之前我们大量进行回调函数时,会采用函数指针的方式,构建一个函数指针数组,调用时按其对应的下标调用即可。但是 lambda 表达式则不然,每一个 lambda 表达式的类型不相同,我们没办法去开辟一个定类型的数组,也就意味着传统的函数指针的方式是不可行的。

包装器的诞生就是为了解决这个问题,通过包装器,可以让功能相似的可调用对象(函数名/函数指针、仿函数对象和 lambda 表达式)的类型统一,使其可以相互联系,相互转化。

二. 什么是包装器?

包装器是个类模板,它的定义在头文件 functional 中
在这里插入图片描述

在这里插入图片描述

下面看看具体的示例:
在这里插入图片描述

也就是说,在 function 的模板列表中,第一个参数是返回值的类型,随后在括号里依次传入形参的类型;而后在赋值的时候,只需要让其等于已经定义过的可调用对象,这样就算包装完成了,最后我们可以使用这个包装好的对象来代替之前的可调用对象,去执行它们的功能。

三. 包装器的使用

还是刚刚的例子,我们分别包函数名/函数指针,仿函数对象,lambda 表达式,看看包装完之后它们各自的类型是什么

// 加法普通函数
int NormalPlus(int num1, int num2)
{
	return num1 + num2;
}

// 加法仿函数
class FunctorPlus
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 加法 lambda 表达式
	auto LambdaPlus = [](int num1, int num2)->int{return num1 + num2; };

	// 使用包装器包装函数名
	function<int(int, int)> function1 = NormalPlus;

	// 使用包装器包装函数地址
	function<int(int, int)> function2 = &NormalPlus;

	// 使用包装器包装仿函数对象(这里的 FunctorPlus() 是一个匿名的仿函数对象)
	function<int(int, int)> function3 = FunctorPlus(); 

	// 使用包装器包装 lambda 表达式
	function<int(int, int)> function4 = LambdaPlus;

	// 其类型都为 class std::function<int __cdecl(int,int)>
	cout << typeid(function1).name() << endl;
	cout << typeid(function2).name() << endl;
	cout << typeid(function3).name() << endl;
	cout << typeid(function4).name() << endl;

	// 它们之间也可以任意赋值
	function1 = function3;
	function3 = function4;

	return 0;
}

------运行结果-------
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>

可以发现,这几个可调用对象无论最开始它们是什么类型,最后都被包装器给包装成了 function<int(int, int)> 的类型。既然类型相同了,它们之间也就可以进行:互相赋值、共同存储在一个数组中的操作了。

通过包装器,我们可以轻轻松松实现函数回调:

int main()
{
	//实现一个计算器
	map<string, function<int(int, int)>> calculator =
	{
		{"加法",[](int a,int b) {return a + b; }},
		{"减法",[](int a,int b) {return a - b; }},
		{"乘法",[](int a,int b) {return a * b; }},
		{"除法",[](int a,int b) {return a / b; }}
	};

	cout << calculator["加法"](10, 20) << endl;
	cout << calculator["减法"](10, 20) << endl;
}

------运行结果-------
30
-10

如果没有包装器,那么只能采用普通函数构建函数指针数组,这既会产生大量命名冲突的风险,又会导致程序的简洁性大大降低。这里如果使用包装器去包装的话,便把 lambda 表达式简洁易读的特点放到了最大。

四. bind 函数模板

1. 为什么要有 bind ?

前面一直说的是普通函数,别忘了还有类中的成员函数,那包装器如何包装和调用成员函数呢?

class A
{
public:
	// 普通成员函数
	int Plus(int num1, int num2)
	{
		return num1 + num2;
	}
	// 静态成员函数
	static int PlusStatic(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 静态成员函数(没有this指针,其实和普通函数没什么区别,函数名和函数地址相同)
	function<int(int, int)> funcStatic1 = A::PlusStatic;
	function<int(int, int)> funcStatic2 = &A::PlusStatic;

	// 非静态成员函数(有this指针,this其实就是该类的对象,位于在参数列表中第一个参数位置)
	// 包装非静态成员函数时,不能用函数名,必须使用函数地址;对非静态成员函数而言,函数名和函数地址不同)
	function<int(A, int, int)> func1 = &A::Plus;

	// 调用包装器对象
	cout << funcStatic1(10, 20) << endl;
	cout << funcStatic2(10, 20) << endl;
	cout << func1(A(), 10, 20) << endl; // 非静态成员函数需要传入一个对象,然后通过这个对象去调用

	return 0;
}

------运行结果-------
30
30
30

总结一下关于成员函数的包装:

  • 类的静态成员函数和普通函数性质一样,它们的函数名和函数地址等价;但是非静态成员函数的地址则必须使用取地址运算符“&”。
  • 包装非静态成员函数时需要增加一个参数(this 指针,this 其实就是一个实例化的类对象),因为非静态成员函数需要用对象去调用,且非静态成员函数的第一个参数是隐藏 this 指针,因此在包装时需要指明第一个形参的类型为类的名称。
  • 静态成员函数因为没有 this 指针,所以它本质上其实和普通函数一样。
  • 调用包装好的非静态成员函数时,注意在参数列表中第一个参数位置传入该类的一个实例化对象,通常都是传的都是匿名对象。

为了让包装好的非静态成员函数使用起来更简单(不想每次使用时都要传入一个实例化对象), C++11 增加了新特性:bind 模板

2. 什么是 bind ?

std::bind 函数定义在头文件 functional 中,是一个函数模板,它也有点像上面的包装器(适配器),接受一个可调用对象(函数/函数名、仿函数对象、lambda 表达式),然后生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收 N 个参数的可调用对象 Func,通过绑定一些参数,返回一个接收 M 个(通常 M <= N)参数的新函数。另外,使用 std::bind 模板还可以修改参数的传参顺序。

具体说的话,bind 可以去给可调用对象(通常是静态成员函数)参数列表中的参数指定缺省值,或者更改形参的接收顺序,然后生成一个新的可调用对象来“适应”原对象的参数列表。

下面是 bind 的定义:
在这里插入图片描述

具体示例:
在这里插入图片描述

在 bind 的第一个参数中,我们输入被绑定的可调用对象的名称,后面再依次输入传参进来的参数的顺序。

3. bind 的使用场景

在绑定时,参数列表中参数的个数和顺序我们可以进行一些小调整:

作用一:给参数设定缺省值
在这里插入图片描述

作用二:调整传参顺序

int normal_plus(int num1, int num2)
{
	return num1 + num2;
}

int main()
{
	// 交换参数的顺序
	function<int(int, int)> fplus = bind
	(	
		normal_plus,
		placeholders::_2,//第一个参数传入 num2
		placeholders::_1 //第二个参数传入 num1
	);

	// 传入参数顺序为绑定的顺序
	fplus(10, 3); //3+10;
}

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

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

相关文章

Elastic Stack--06--JavaAPI----索引(创建-查询- 删除)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 环境准备添加依赖&#xff1a;HelloElasticsearch JavaAPI-索引1.创建2.查询3.删除 环境准备 添加依赖&#xff1a; <dependencies><dependency><g…

第G3周:CGAN入门|生成手势图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、前置知识 CGAN&#xff08;条件生成对抗网络&#xff09;的原理是在原始GAN的基础上&#xff0c;为生成器和判别器提供 额外的条件信息…

vue3 ref获取子组件显示 __v_skip : true 获取不到组件的方法 怎么回事怎么解决

看代码 问题出现了 当我想要获取这个组件上的方法时 为什么获取不到这个组件上的方法呢 原來&#xff1a; __v_skip: true 是 Vue 3 中的一个特殊属性&#xff0c;用于跳过某些组件的渲染。当一个组件被标记为 __v_skip: true 时&#xff0c;Vue 将不会对该组件进行渲染&am…

ABAP接口-RFC连接(ABAP TO ABAP)

目录 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09;创建ABAP连接RFC函数的调用 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09; 创建ABAP连接 事务代码&#xff1a;SM59 点击创建&#xff0c;填写目标名称&#xff0c;选择连接类型&#xff1a; 填写主机名…

打卡--MySQL8.0 一(单机部署)

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; MySQL 8.0 简介 MySQL 8.0与5.7的区别主要体现在&#xff1a;1、性能提升&#xff1b;2、新的默认…

02-app端文章查看,静态化freemarker,分布式文件系统minIO

app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO 1)文章列表加载 1.1)需求分析 文章布局展示 1.2)表结构分析 ap_article 文章基本信息表 ap_article_config 文章配置表 ap_article_content 文章内容表 三张表关系分析 1.3)导入文章数据库 1.3.1)导入数据…

【vue.js】文档解读【day 2】 | 响应式基础

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 响应式基础声明响应式状态(属性)响应式代理 vs 原始值声明方法深层响应性DOM 更新时机有状态方法 响应式…

电脑数据安全新防线:文件备份的终极指南

一、数据守护者的使命&#xff1a;文件备份的重要性 在数字化日益普及的今天&#xff0c;电脑已成为我们日常生活和工作的必备工具&#xff0c;文件作为我们储存、交流和处理信息的主要载体&#xff0c;其重要性不言而喻。然而&#xff0c;无论是由于硬件故障、软件崩溃&#…

Autosar教程-Mcal教程-GPT配置教程

3.3GPT配置、生成 3.3.1 GPT配置所需要的元素 GPT实际上就是硬件定时器,需要配置的元素有: 1)定时器时钟:定时器要工作需要使能它的时钟源 2)定时器分步:时钟源进到定时器后可以通过分频后再给到定时器 定时器模块选择:MCU有多个定时器模块,需要决定使用哪个定时器模块作…

【动态规划】代码随想录算法训练营第三十九天 |62.不同路径,63.不同路径II(待补充)

62.不同路径 1、题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2、文章讲解&#xff1a;代码随想录 3、题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右…

JavaScript高级Ⅱ(全面版)

接上文 JavaScript高级Ⅰ JavaScript高级Ⅰ(自认为很全面版)-CSDN博客 目录 第2章 DOM编程 2.1 DOM编程概述 2.1.4 案例演示(商品全选) 2.1.5 dom操作内容 代码演示&#xff1a; 运行效果&#xff1a; 2.1.6 dom操作属性 代码演示&#xff1a; 运行效果&#xff1a; 2…

H264/265编码参数2: Profile Level Tier

profile和level profile和level是视频编码中两个很重要的概率&#xff0c;中文一般叫做档次和级别。 在MPEG2标准里边&#xff0c;按不同的压缩比分成五个档次&#xff0c;按视频清晰度分为四个级别&#xff0c;如下图所示&#xff1a; 档次和级别共有 20 种组合&#xff0c;…

2024年【化工自动化控制仪表】考试总结及化工自动化控制仪表作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【化工自动化控制仪表】考试总结及化工自动化控制仪表作业考试题库&#xff0c;包含化工自动化控制仪表考试总结答案和解析及化工自动化控制仪表作业考试题库练习。安全生产模拟考试一点通结合国家化工自动化控…

day-18 长度最小的子数组

运用队列的思维&#xff0c;求出每种满足题意的子数组长度&#xff0c;最小的即为答案&#xff0c;否则返回0 code class Solution {public int minSubArrayLen(int target, int[] nums) {int l0,r0;int ansInteger.MAX_VALUE;int total0;while(r<nums.length){totalnums[r…

C++:类和对象(三)——拷贝构造函数和运算符重载

目录 一、拷贝构造函数 1.概念 2.特性 二、赋值运算符重载 1.运算符重载 2.赋值运算符重载 &#xff08;1&#xff09;注意的点&#xff1a; &#xff08;2&#xff09;赋值运算符不允许被重载为全局函数&#xff0c;只能重载为类的成员函数 &#xff08;3&#xff09;…

STM32单片机示例:ETH_LAN8742_DHCP_NonOS_Poll_H743

文章目录 目的基础说明关键配置关键代码示例链接总结 目的 以太网是比较常用到的功能&#xff0c;STM32系列单片机使用CubeMX配置使用以太网功能比非常方便。不过对于H7系列来说需要使能 DCache 才能启用LwIP&#xff0c;启用Cache后又会带来一些需要特别注意的事情。这篇文章…

HarBor私有镜像仓库安装部署

环境准备 #>>> redis $ yum -y install redis $ systemctl enable --now redis $ vim /etc/redis.conf modify: bind <ipaddress> $ systemctl restart redis#>>> nfs $ yum -y install nfs-utils $ mkdir -p /data/harbor $ vi /etc/exports /data/h…

简介:CMMI软件能力成熟度集成模型

前言 CMMI是英文Capability Maturity Model Integration的缩写。 CMMI认证简称软件能力成熟度集成模型&#xff0c;是鉴定企业在开发流程化和质量管理上的国际通行标准&#xff0c;全球软件生产标准大都以此为基点&#xff0c;并都努力争取成为CMMI认证队伍中的一分子。 对一个…

动静态库

inode inode用于管理文件属性和内容 一个文件只能有一个inode&#xff0c;一个inode可以对应多个文件名 Linux进程中&#xff0c;打开的每一个文件都有对应的文件inode属性和文件页缓冲区&#xff08;内存和磁盘的缓冲区&#xff09; 软硬链接 硬链接 多个文件指向同一个i…

Gradle模块化最佳实践

一&#xff0c;模块化的原因及意义 模块化是一种将大型的软件系统拆分成相互独立的模块的方法。具有以下优势&#xff1a; 代码复用&#xff1a;不同的模块可以共享相同的代码。这样可以避免重复编写相同的代码&#xff0c;提高开发效率。模块独立性&#xff1a;每个模块都可…