【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的学习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

相关文章:
【C++】C++11—— 包装器
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C学习 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C】C11…...
LED显示屏高刷新率和低刷新率有什么区别
LED显示屏的刷新率是指图像在LED显示屏上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。LED显示屏的刷新率越高,图像闪烁感就越小,稳定性也就越高,换言之对视力的保护也越好…...
国际伦敦银点差费值得吗?
伦敦银是国际轨技术属市场上广受追捧的白银保证金交易品种,具有交易时长、交易制度灵活、资金利用率高等诸多的优点。 国际伦敦银的优势主要来自它所实行的是保证金交易制度。目前香港平台一般执行的保证金比例标准是2%,以目前22美元/盎司左右的白银价格…...
常见的作物模型应用技巧!DSSAT模型、APSIM模型、WOFOST模型与PCSE模型等应用
①最新DSSAT作物模型建模方法及应用 DSSAT模型内核算法是基于Fortran语言开发的,软件界面是基于C进行开发。了解和熟悉DSSAT模型的关键算法和软件的操作是学习DSSAT模型的基础。此外,想要成为一名优秀的作物模型使用者与科研团队不可或缺的人才ÿ…...
2023年中国超硬材料制品分析及超硬刀具市场规模分析[图]
超硬材料是指硬度特别高的材料,可分为天然以及人造两种,前者主要包括天然的钻石(金刚石)、黑钻石,后者则包括人造金刚石、立方氮化硼。 超硬材料制品分类 资料来源:共研产业咨询(共研网&#x…...
使用React、Express实现一个问卷发布/收集系统
1. 设置项目结构 questionnaire-system/client/ // 前端应用src/components/ // React组件pages/ // 页面App.jsindex.jsserver/ // 后端服务routes/ // 路由models/ // 数据模型app.jspackage.json2. 启动前端应用…...
DDD之上下文映射图(Context Mapping)
领域驱动设计系列文章,点击上方合集↑ 1. 开头 在DDD中,限界上下文与限界上下文之间需要相互集成,这种集成关系在DDD中称为上下文映射(Context Mapping),也就是子域与子域之间的集成关系。 所以首先我们…...
CountDownLatch的原理
使用CountDownLatch可以实现等待多个线程执行完毕的功能,实现线程之间的协调,让它们按照我们期望的顺序执行,从而避免了可能出现的并发问题。 CountDownLatch是如何实现主线程等待子线程全部结束的呢? 代码用例 这里我们使用一段…...
Java新特性Stream流详解
一、概述 Stream流是Java 8 API添加的一个新的抽象,以一种声明性方式处理数据集合(侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式)。 Stream流是对集合(Collection)对象功能的增强…...
关于VScode中一些常用的快捷操作!
vscode CTRLO:打开文件夹以开始工作 先CTRLK 再CTRLO:打开文件夹以开始工作 如何选择workspace:file → open folder→选目标文件夹【当前工作区选择会影响代码是否能运行】 如何打开终端:View → terminal debug看不到变化历史&…...
Django 使用Mysql数据库
目录 Django 使用Mysql数据库本地安装Mysql数据服务安装好Pymysql服务Django配置数据库迁移各种报错无法找到mysqlclient数据库拒绝连接 Django 使用Mysql数据库 本地安装Mysql数据服务 安装好Pymysql服务 python3 -m pip install PyMySQL官方文档介绍 Django配置 官网文档 …...
js继承的几种方式(原型链继承、构造函数继承、组合式继承、寄生组合式继承、ES6的Class类继承)
1.原型链继承 实现原理:子类的原型指向父类实例。子类在自身实例上找不到属性和方法时去它父类实例(父类实例和实例的原型对象)上查找,从而实现对父类属性和方法的继承 缺点: 子类创建时不能传参(即没有…...
AnyTransition/过渡动画, MatchedGeometryEffect/匹配几何动画效果 的使用
1. AnyTransition 过渡动画效果 1.1 创建过度动画案例 AnyTransitionBootcamp.swift import SwiftUI/// 旋转修饰 View struct RotateViewModifier :ViewModifier{let rotation: Doublefunc body(content: Content) -> some View {content.rotationEffect(Angle(degrees: r…...
mac版postman升级后数据恢复办法
postman升级了一下,所有的collections都丢失了。 首先在finder里找到这个路径 /Users/{用户名}/Library/Application Support/Postman找到升级之前的的最新的backup.json,然后在postman里import这个文件。 所有升级前的collections都恢复了࿰…...
四.镜头知识之放大倍率
四.镜头知识之放大倍率 文章目录 四.镜头知识之放大倍率4.0 前言4.1 镜头的光学放大倍率的计算方法4.2 显示器的电子放大倍率4.2.1 智能硬件产品的显示放大倍率计算案例4.3 系统放大倍率4.4 智能硬件产品的系统放大倍率计算案例4.4 智能硬件产品的系统放大倍率计算案例4.0 前言…...
Jenkins UI 自动化持续化集成测试
一:安装jenkins 环境 在官网下载msi 直接安装即可 二:设置全局变量 设置allure 路径 三:创建项目 1、创建自由风格项目 2、如果项目在本地,且本地服务器是windows ,找到Jenkins安装根目录,寻找config…...
vue项目中引入地图的详细教程
第一步:在项目中安装地图插件 npm i amap/amap-jsapi-loader --save 第二步:创建一个容器 添加id属性 (因为地图必须使用id 不能使用class) <div id"maps"></div> 第三步:给这个容器设置宽…...
MyBatisPlus 多数据源配置
目录 一、mybatis-plus 简介 特性 二、支持数据库: 三、 开发实例 1. 引入依赖: 2. 参数配置application.yml 3. 在 Spring Boot 启动类中添加 MapperScan 注解,扫描 Mapper 文件夹: 4. 编写实体类 User.java(此处…...
使用Golang实现HTTP代理突破IP访问限制
引言 在当今互联网时代,网站和服务商为了维护安全性和保护用户隐私,常常会对特定的IP地址进行封锁或限制。但是,有时候我们可能需要访问这些被限制的网站或服务。为了突破这种限制,我们可以使用HTTP代理来隐藏真实的客户端IP地址…...
Iterator和ListIterator的区别是什么?
Iterator 和 ListIterator 都是 Java 集合框架中的迭代器,其中 Iterator 是普遍适用于所有实现了 Iterable 接口的集合类的通用迭代器,而 ListIterator 则是专门用于遍历 List 集合的迭代器,它比 Iterator 更加强大,而且只适用于 List 集合。 以下是 Iterator 和 ListItera…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
