【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,//第一个参数传入 num2placeholders::_1 //第二个参数传入 num1);// 传入参数顺序为绑定的顺序fplus(10, 3); //3+10;
}

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

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

相关文章

Vue.js计算属性:实现数据驱动的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

RESTful API学习

RESTful API REST&#xff08;英文&#xff1a;Representational State Transfer&#xff0c;简称REST&#xff0c;直译过来表现层状态转换&#xff09;是一种软件架构风格、设计风格&#xff0c;而不是标准&#xff0c;只是提供了一组设计原则和约束条件。它主要用于客户端和…

光伏数字化管理平台:驱动绿色能源革命的智能化引擎

随着全球对可再生能源需求的不断增长&#xff0c;光伏产业已经成为推动绿色能源革命的重要力量。在这个背景下&#xff0c;光伏数字化管理平台应运而生&#xff0c;以其强大的数据处理、实时监控和智能优化功能&#xff0c;为光伏电站的运营管理和维护带来了革命性的变革。 光伏…

储能系统--户用储能美洲市场(三)

2、美洲市场 2.1、美国户储发展驱动力 &#xff08;1&#xff09;电网老化带来配储需求&#xff0c;户用光储成家庭第二用电保障 美国大部分电网建于20世纪60和70年代&#xff0c;超70%以上的输电系统已经超过了25年&#xff0c;在高负荷运转或者外部环境承压时&#xff0c;…

深入理解Hive:探索不同的表类型及其应用场景

文章目录 1. 引言2. Hive表类型概览2.1 按照数据存储位置2.2 按照数据管理方式2.3 按照查询优化2.4 按照数据的临时性和持久性 3. 写在最后 1. 引言 在大数据时代&#xff0c;Hive作为一种数据仓库工具&#xff0c;为我们提供了强大的数据存储和查询能力。了解Hive的不同表类型…

【数学建模】层次分析

1.建立递阶层次结构模型 2.构造出各层次中的所有判断矩阵 对指标的重要性进行两两比较&#xff0c;构造判断矩阵&#xff0c;科学求出权重 矩阵中元素aij的意义是&#xff0c;第i个指标相对第j个指标的重要程度 对角线1&#xff0c;aijaji1 矛盾——>一致性检验

网络安全:OpenEuler 部署 jumpserver 堡垒机

目录 一、实验 1.环境 2.OpenEuler 部署 jumpserver 堡垒机 3.OpenEuler 使用 jumpserver 堡垒机&#xff08;管理Linux&#xff09; 4.OpenEuler 使用 jumpserver 堡垒机&#xff08;管理Windows&#xff09; 二、问题 1.jumpserver 安装报错 一、实验 1.环境 &#x…

【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表

文章目录 题目思路解答 题目 简单 相关标签 相关企业 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入…

【Linux】shell理解及linux权限解读(“花花公子Root”的自由人生)

目录 1.shell外壳理解 1.1 什么是shell外壳&#xff1a; 1.2 为什么存在shell外壳程序&#xff1a; 1.3外壳程序的具体工作阶段是怎么样的&#xff1f;&#xff08;招实习生&#xff0c;工作失败也不影响公司&#xff09; 2.linux下的权限的概念 2.1linux的用户 2.2.文件类型和…

“每一次的感应,都是对环境的温柔拥抱。”#STM32项目二 《感应开关盖垃圾桶》【下】

“每一次的感应&#xff0c;都是对环境的温柔拥抱。”#STM32项目二 《感应开关盖垃圾桶》【下】 前言预备知识1.实现距离感应开盖1.1换另一个定时器进行PWM输出驱动SG90舵机1.2延用超声波传感器介绍及实战工程进行配置PWM输出1.3在主C文件合适位置封装开关盖&#xff0c;开关LE…

Understanding Vulkan Objects

​ 和学习其他API一样&#xff0c;学习Vulkan API中有一个重要部分&#xff1a;了解Vulkan API定义了拿下类型&#xff0c;以及这些类型之间的关系。为了帮助理解这些类型&#xff0c;接下来会绘制一幅关系图&#xff0c;表现它们之间的关系&#xff0c;尤其是创建依赖关系。 …

map和set(二)——AVL树的简单实现

引入 二叉搜索树有其自身的缺陷&#xff0c;假如往树中 插入的元素有序或者接近有序&#xff0c;二叉搜索树就会退化成单支树&#xff0c;时间复杂度会退化成O(N)&#xff0c;因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理&#xff0c;即采用平衡树来实现。简…

康奈尔开源近10万份审稿意见,未来论文发表或将由AI定夺

大语言模型&#xff08;LLMs&#xff09;的进步为自动化论文评审开辟了新途径&#xff0c;这些模型在学术反馈领域展现出巨大潜力。自动化评审的核心优势在于其能够精准指出论文草稿的不足之处&#xff0c;助力作者优化研究。尽管已有丰富的同行评审数据&#xff0c;但现有自动…

20.2 nginx

20.2 nginx 1. 学习目标2. 介绍2.1 正向代理2.2 反向代理2.3 动态静态资源分离2.4 nginx优缺点3. 安装3.1 Linux安装****************************************************************************************************************************************************…

基于stm32的流水灯设计

1基于stm32的流水灯设计[proteus仿真] 速度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的自行车测速系统设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&#xff0c…

《领导的气场——8堂课讲透中国式领导智慧》读书笔记

整体感悟 个人感觉书籍比较偏说教、理论&#xff0c;没有看完。 现仅仅摘录自己“心有戚戚焉”的内容。 经典摘录 管理的本质是通过别人完成任务。有一百件事情&#xff0c;一个人都做了&#xff0c;那只能叫勤劳&#xff1b;有一百件事情&#xff0c;主事的人自己一件也不做&…

js 获取浏览器相关的宽高尺寸

window 屏幕 屏幕分辨率的高&#xff1a; window.screen.height 屏幕分辨率的宽&#xff1a; window.screen.width 屏幕可用工作区高度&#xff1a; window.screen.availHeight 屏幕可用工作区宽度&#xff1a; window.screen.availWidth document 网页 网页可见区域宽&#xf…

C语言学习--练习4(二维数组)

目录 1.统计有序数组中的负数 2.矩阵对角线元素和 3.最富有客户的资产总量 4.托普利兹矩阵 5.矩阵中的幸运数 6.二进制矩阵中的特殊位置 7.岛屿的周长 1.统计有序数组中的负数 //直接遍历二维数组即可 int countNegatives(int** grid, int gridSize, int* gridColSize) …

比特币普通地址、隔离见证(兼容)、隔离见证(原生)、Taproot 地址傻傻分不清楚

我们在使用比特币钱包的时候&#xff0c;可以看到各种地址类型&#xff1a;普通地址、隔离见证&#xff08;兼容&#xff09;、隔离见证&#xff08;原生&#xff09;、Taproot 地址。 看得我们一脸懵逼&#xff0c;为什么会有这么多种类型的地址&#xff1f; 它们之间都有什么…

选修-单片机作业第1/2次

第一次作业 第二次作业 1、51 系列单片机片内由哪几个部分组成&#xff1f;各个部件的最主要功能是什么&#xff1f; 51系列单片机的内部主要由以下几个部分组成&#xff0c;每个部件的主要功能如下&#xff1a; 1. **中央处理器&#xff08;CPU&#xff09;**&#xff1a;这是…