【C++】C++11—— 包装器

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++学习
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【C++】C++11 ——lambda表达式

文章目录

  • 包装器
    • function包装器
      • function包装器介绍
      • function包装器统一类型
      • function包装器简化代码
      • function包装器的意义
    • bind包装器
      • bind包装器介绍
      • bind包装器绑定固定参数
      • bind包装器调整传参顺序
      • bind包装器的意义
  • 总结:

包装器

function包装器

function包装器介绍

function包装器

function是一种函数包装器,也叫做适配器。它可以对可调用对象进行包装,C++中的function本质就是一个类模板。

function类模板的原型如下:

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

模板参数说明:

Ret:被包装的可调用对象的返回值类型。
Args…:被包装的可调用对象的形参类型。

包装示例

function包装器可以对可调用对象进行包装,包括函数指针(函数名)、仿函数(函数对象)、lambda表达式、类的成员函数。比如:

int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator()(int a, int b){return a + b;}
};
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{//1、包装函数指针(函数名)function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;//2、包装仿函数(函数对象)function<int(int, int)> func2 = Functor();cout << func2(1, 2) << endl;//3、包装lambda表达式function<int(int, int)> func3 = [](int a, int b){return a + b; };cout << func3(1, 2) << endl;//4、类的静态成员函数//function<int(int, int)> func4 = Plus::plusi;function<int(int, int)> func4 = &Plus::plusi; //&可省略cout << func4(1, 2) << endl;//5、类的非静态成员函数function<double(Plus, double, double)> func5 = &Plus::plusd; //&不可省略cout << func5(Plus(), 1.1, 2.2) << endl;return 0;
}

注意事项:
包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以像普通函数一样使用了。
取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”。
包装非静态的成员函数时需要注意,非静态成员函数的第一个参数是隐藏this指针,因此在包装时需要指明第一个形参的类型为类的类型。

function包装器统一类型

对于以下函数模板useF:

传入该函数模板的第一个参数可以是任意的可调用对象,比如函数指针、仿函数、lambda表达式等。
useF中定义了静态变量count,并在每次调用时将count的值和地址进行了打印,可判断多次调用时调用的是否是同一个useF函数。

代码如下:

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;//lambda表达式cout << useF([](double d)->double{return d / 4; }, 11.11) << endl;return 0;
}

由于函数指针、仿函数、lambda表达式是不同的类型,因此useF函数会被实例化出三份,三次调用useF函数所打印count的地址也是不同的。

但实际这里根本没有必要实例化出三份useF函数,因为三次调用useF函数时传入的可调用对象虽然是不同类型的,但这三个可调用对象的返回值和形参类型都是相同的。
这时就可以用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数,这时就只会实例化出一份useF函数。
根本原因就是因为包装后,这三个可调用对象都是相同的function类型,因此最终只会实例化出一份useF函数,该函数的第一个模板参数的类型就是function类型的。
代码如下:

int main()
{//函数名function<double(double)> func1 = func;cout << useF(func1, 11.11) << endl;//函数对象function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;//lambda表达式function<double(double)> func3 = [](double d)->double{return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}

这时三次调用useF函数所打印count的地址就是相同的,并且count在三次调用后会被累加到3,表示这一个useF函数被调用了三次。

function包装器简化代码

求解逆波兰表达式的步骤如下:
定义一个栈,依次遍历所给字符串。
如果遍历到的字符串是数字则直接入栈。
如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果。

代码如下:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for (const auto& str : tokens){int left, right;if (str == "+" || str == "-" || str == "*" || str == "/"){right = st.top();st.pop();left = st.top();st.pop();switch (str[0]){case '+':st.push(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;default:break;}}else{st.push(stoi(str));}}return st.top();}
};

在上述代码中,我们通过switch语句来判断本次需要进行哪种运算,如果运算类型增加了,比如增加了求余、幂、对数等运算,那么就需要在switch语句的后面中继续增加case语句。

这种情况可以用包装器来简化代码。

建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行。
当运算类型增加时,就只需要建立新增运算符与其对应函数之间的映射关系即可。

代码如下:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;unordered_map<string, function<int(int, int)>> opMap = {{ "+", [](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; } }};for (const auto& str : tokens){int left, right;if (str == "+" || str == "-" || str == "*" || str == "/"){right = st.top();st.pop();left = st.top();st.pop();st.push(opMap[str](left, right));}else{st.push(stoi(str));}}return st.top();}
};

需要注意的是,这里建立的是运算符与function类型之间的映射关系,因此无论是函数指针、仿函数还是lambda表达式都可以在包装后与对应的运算符进行绑定。

function包装器的意义

将可调用对象的类型进行统一,便于我们对其进行统一化管理。
包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用。

bind包装器

bind包装器介绍

bind包装器

bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,C++中的bind本质是一个函数模板。

bind函数模板的原型如下:

template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

模板参数说明:

fn:可调用对象。
args…:要绑定的参数列表:值或占位符。

调用bind的一般形式

调用bind的一般形式为:auto newCallable = bind(callable, arg_list);

解释说明:

callable:需要包装的可调用对象。
newCallable:生成的新的可调用对象。
arg_list:逗号分隔的参数列表,对应给定的callable的参数。当调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。

此外,除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。

bind包装器绑定固定参数

无意义的绑定

下面这种绑定就是无意义的绑定:

int Plus(int a, int b)
{return a + b;
}
int main()
{//无意义的绑定function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);cout << func(1, 2) << endl; //3return 0;
}

绑定时第一个参数传入函数指针这个可调用对象,但后续传入的要绑定的参数列表依次是placeholders::_1和placeholders::_2,表示后续调用新生成的可调用对象时,传入的第一个参数传给placeholders::_1,传入的第二个参数传给placeholders::_2。此时绑定后生成的新的可调用对象的传参方式,和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定。

绑定固定参数

如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10。比如:

int Plus(int a, int b)
{return a + b;
}
int main()
{//绑定固定参数function<int(int)> func = bind(Plus, placeholders::_1, 10);cout << func(2) << endl; //12return 0;
}

此时调用绑定后新生成的可调用对象时就只需要传入一个参数,它会将该值与10相加后的结果进行返回。

bind包装器调整传参顺序

调整传参顺序

对于下面Sub类中的sub成员函数,sub成员函数的第一个参数是隐藏的this指针,如果想要在调用sub成员函数时不用对象进行调用,那么可以将sub成员函数的第一个参数固定绑定为一个Sub对象。比如:

class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//绑定固定参数function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << func(1, 2) << endl; //-1return 0;
}

此时调用绑定后生成的可调用对象时,就只需要传入用于相减的两个参数了,因为在调用时会固定帮我们传入一个匿名对象给this指针。

如果想要将sub成员函数用于相减的两个参数的顺序交换,那么直接在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了。比如:

class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//调整传参顺序function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);cout << func(1, 2) << endl; //1return 0;
}

根本原因就是因为,后续调用新生成的可调用对象时,传入的第一个参数会传给placeholders::_1,传入的第二个参数会传给placeholders::_2,因此可以在绑定时通过控制placeholders::_n的位置,来控制第n个参数的传递位置。

bind包装器的意义

将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
可以对函数参数的顺序进行灵活调整。

总结:

今天我们学习了C++11中lambda表达式,了解了一些有关的底层原理。接下来,我们将继续进行C++11的学习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

2023年中国TFT-LCD面板产业链、需求量及市场规模分析[图]

按显示技术尺寸来分&#xff0c;主要分为大尺寸和小尺寸&#xff08;10寸以下&#xff09;&#xff0c;TFT-LCD行业下游主要是各类型消费电子&#xff0c;包括PC显示器&#xff0c;液晶电视&#xff0c;智能手机等。 TFT&#xff0d;LCD面板产业链 资料来源&#xff1a;共研产…

【广州华锐互动】利用VR开展细胞基础实验教学有什么好处?

在科技发展的驱动下&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已被广泛应用于各个领域&#xff0c;包括教育和医学。尤其是在医学教育中&#xff0c;VR技术已成为一种革新传统教学模式的有效工具。本文将探讨使用VR进行细胞基础实验教学的优势。 首先&#xff0c;VR技…

(SCADA)监控与数据采集系统

SCADA应用于监控和采集数据来控制工业过程软件。它通过收集和传输实时数据&#xff0c;帮助操作员监视和控制各种设备和过程。这篇文章将介绍SCADA系统的工作原理、应用行业和场景&#xff0c;以及所需的模块和硬件设备。最后&#xff0c;还会详细介绍几款国外SCADA软件给大家参…

VUE整合Echarts实现简单的数据可视化

文章目录 前言 一、Echarts的安装 二、可视化渲染 1.柱状图 2.饼图 3.主题的下载 总结 前言 ECharts是一款功能强大的前端数据可视化库&#xff0c;支持多种图表类型和统计图表、地理数据可视化、关系型数据展示、多维数据处理和商业智能功能。通过广泛的图表类型、统计分析…

FPGA面试题(5)

一.FPGA可以综合实现为RAM/ROM/CAM的三种资源及注意事项 三种资源&#xff1a;BLOCK RAM&#xff0c;触发器&#xff08;FF&#xff09;&#xff0c;查找表&#xff08;LUT&#xff09; 注意事项&#xff1a; 1.生成RAM&#xff0c;首选BLOCK RAM。因为BLOCK RAM是已经存在的“…

Springboot项目中加载Groovy脚本并调用其内部方代码实现

前言 项目中部署到多个煤矿的上&#xff0c;每一种煤矿的情况都相同&#xff0c;涉及到支架的算法得写好几套&#xff0c;于是想到用脚本实现差异变化多的算法&#xff01;一开始想到用java调用js脚本去实现&#xff0c;因为这个不需要引入格外的包&#xff0c;js对我来说也没…

测试需要写测试用例吗?

如何理解软件的质量 我们都知道&#xff0c;一个软件从无到有要经过需求设计、编码实现、测试验证、部署发布这四个主要环节。 需求来源于用户反馈、市场调研或者商业判断。意指在市场行为中&#xff0c;部分人群存在某些诉求或痛点&#xff0c;只要想办法满足这些人群的诉求…

docker部署多个node-red操作过程

docker部署多个node-red操作过程 一、docker安装教程二、docker安装node-red2.1 拉取镜像2.2 创建目录并分配权限 三、 docker操作node-red3.1 部署node-red3.2 查看\关闭\删除容器 四、Docker删除Redis镜像 回到目录 一、docker安装教程 【docker使用安装教程】 回到目录 …

NI GPIB-140A 使用缓冲传输技术 边缘人工智能

NI GPIB-140A 使用缓冲传输技术 边缘人工智能 GPIB总线扩展器—GPI b-140 a可以将GPIB系统的电缆长度延长一千米&#xff0c;而不会影响GPIB的完整性&#xff0c;也不需要修改软件。该配件使用缓冲传输技术&#xff0c;以高达1.1 Mb/s (IEEE 488.1)或2.8 Mb/s (HS488)的速率传…

安科瑞预付费系统在某大型连锁农贸市场的设计应用

安科瑞 崔丽洁 摘要 本远程预付费管理系统采用智能远程预付费电表&#xff08;DTSY1352-NK/DDSY1352-NK系列&#xff09;&#xff0c;NB智能远传水表&#xff0c;采集各商户实时用电量、用电量总数&#xff0c;通过平台定时结算&#xff0c;结算账户余额&#xff0c;从而进行智…

4d动感影院座椅5d动感影院体验馆大型7D互动影院

今天来讲一下市场上现在受欢迎的5d7d影院&#xff0c;组成部分&#xff0c;落地方案 5D影院的系统组成部分&#xff1a;1、动感座椅、2、投影幕(银幕)和投影机、3、音箱、4、各种的动感特效、5、5d影院眼镜&#xff0c;很简单的组成硬件&#xff0c;就可以组成一套的5d影院设备…

专题三:穷举、暴搜、深搜、回溯、剪枝【递归、搜索、回溯】

1、全排列 class Solution { public:vector<vector<int>> ret;vector<int> path;bool check[7];void dfs(vector<int>& nums){if(nums.size() path.size()) {ret.push_back(path);return;}for(int i 0;i < nums.size();i){if(check[i] fals…

HBase基础

HBase基础 参考 https://www.bilibili.com/video/BV1bC4y1b7Q1 HBase 简介 定义 HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库(k-v)。 数据量越大&#xff0c;优势越明显&#xff1b;数据量小&#xff0c;比较消耗内存&#xff0c;耗资源&#xff1b;数据量大…

如何优雅的实现接口统一调用

耦合问题 有些时候我们在进行接口调用的时候&#xff0c;比如说一个push推送接口&#xff0c;有可能会涉及到不同渠道的推送&#xff0c;以我目前业务场景为例&#xff0c;我做结算后端服务的&#xff0c;会与金蝶财务系统进行交互&#xff0c;那么我结算后端会涉及到多个结算…

无线通信——Mesh体系结构

Mesh体系结构 了解Mesh的体系结构有利于我们后期的理解&#xff01;这里有些简单的知识点以及解释 WMN&#xff1a;WMN就是无线mesh网络&#xff0c;我们一般提到WMN其实特指就是Mesh网络的整个体系结构 STA&#xff1a;客户工作站&#xff0c;这个东西就是用户的设备端&…

《软件方法》2023版第1章(09)基本共识上的沟通,SysML

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 1.3 UML 1.3.2 使用UML的理由 1.3.2.5 基本共识上的沟通 符号标准并不是随便哪个人拍脑袋定下来&#xff0c;然后毫无道理地强迫大家接受。符号背后往往隐含着我们对某个学科的一些…

SpringAOP是什么?为什么要有SpringAOP?

SpringAOP是什么&#xff1f;为什么要有SpringAOP&#xff1f; 原文&#xff1a;SpringAOP是什么&#xff1f;为什么要有SpringAOP&#xff1f; 一、有SpringAOP之前 简单的开发场景&#xff0c;只需要写简单的业务逻辑&#xff0c;比如CRUD 但是在执行具体的逻辑之前&…

Python中兔子递归函数的例子

本文将详细介绍Python中兔子递归函数的例子&#xff0c;展示递归函数的基本实现方法及其原理。 一、递归函数的概念 递归函数是指在函数内部调用自身的函数。通过递归函数&#xff0c;可以将复杂问题分解成简单的子问题来解决。 这种过程是有限的&#xff0c;当子问题足够小…

【大数据Hive】hive select 语法使用详解

目录 一、前言 二、Hive select 完整语法树 三、Hive select 操作演示 3.1 数据准备 3.1.1 创建一张表 3.1.2 将数据load加载到t_usa_covid19表 3.1.3 再创建一张分区表 3.1.4 使用动态分区插入数据 3.2 select 常用语法 3.2.1 查询所有字段或者指定字段 3.2.2 查询…

kafka安装和使用的入门教程

这篇文章简单介绍如何在ubuntu上安装kafka&#xff0c;并使用kafka完成消息的发送和接收。 一、安装kafka 访问kafka官网Apache Kafka&#xff0c;然后点击快速开始 紧接着&#xff0c;点击Download 最后点击下载链接下载安装包 如果下载缓慢&#xff0c;博主已经把安装包上传…