【C++11】包装器

包装器

  • 一、function包装器
    • 1、function包装器介绍
    • 2、包装示例
    • 3、function包装器统一类型
    • 4、function包装器简化代码
    • 5、function包装器的意义
  • 二、bind包装器
    • 1、bind包装器介绍
      • (1)bind包装器
      • (2)调用bind的一般形式
    • 2、bind包装器绑定固定参数
      • (1)无意义的绑定
      • (2)绑定固定参数
    • 3、bind包装器调整传参顺序
      • (1)调整传参顺序
      • (2)bind包装器的意义


一、function包装器

1、function包装器介绍

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

function类模板的原型如下:

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

模板参数说明

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

2、包装示例

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

#define _CRT_CURTUE_WARNINGS 1
#include<iostream>
#include<functional>
using namespace std;// 函数
int f(int a, int b)
{return a + b;
}
// 仿函数
struct Function
{int operator()(int a, int b){return a + b;}
};
// plus类
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;std::cout << func1(1, 2) << std::endl;// 2、包装仿函数(函数对象)function<int(int, int)> func2 = Function();std::cout << func2(2, 4) << std::endl;// 3、包装lambda表达式function<int(int, int)> func3 = [](int a, int b) {return a + b; };std::cout << func3(6, 8) << std::endl;// 4、包装类内静态成员函数function<int(int, int)> func4 = &Plus::plusi; // &可以省略std::cout << func4(7, 9) << std::endl;// 5、包装类内非静态成员函数function<double(Plus, double, double)> func5 = &Plus::plusd; // &不可省略std::cout << func5(Plus(), 1.1, 2.2) << std::endl;return 0;
}

在这里插入图片描述

注意

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

3、function包装器统一类型

对于以下函数模板useF:

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

useF代码如下:

template<class F, class T>
T useF(F f, T t)
{static int count = 0;std::cout << "count:" << ++count << std::endl;std::cout << "count:" << &count << std::endl;return f(x);
}

在传入第二个参数类型相同的情况下,如果传入的可调用对象的类型是不同的,那么在编译阶段该函数模板就会被实例化多次。

// 函数
double f(double a)
{return a / 2;
}
// 仿函数
struct Function
{
public:double operator()(double a){return a / 4;}
};int main()
{// 1、函数指针std::cout << useF(f, 11.11) << std::endl;// 2、仿函数std::cout << useF(Function(), 11.11) << std::endl;// 3、lambda表达式std::cout << useF([](double d)->double {return d / 4; }, 11.11) << std::endl;return 0;
}

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

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

如下代码:

int main()
{// 1、函数指针function<double(double)> func1 = f;std::cout << useF(func1, 11.11) << std::endl;// 2、仿函数function<double(double)> func2 = Function();std::cout << useF(func2, 11.11) << std::endl;// 3、lambda表达式function<double(double)> func3 = [](double x)->double {return x / 4; };std::cout << useF(func3, 11.11) << std::endl;return 0;
}

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

4、function包装器简化代码

我们用我们的老朋友–逆波兰表达式来进行讲解:

class Solution
{
public:int evalRPN(vector<string>& tokens){// 定义一个栈stack<int> t;// 从数组中选择for (const auto& str : tokens){int left;int right;// 如果是+-*/操作符则进入switchif (str == "+" || str == "-" && str == "*" && str == "/"){// 取出栈顶两个元素left = t.top();t.pop();right = t.top();t.pop();// switchswitch (str[0]){case '+':t.push(left + right);break;case '-':t.push(left - right);break;case '*':t.push(left * right);break;case '/':if (right == 0){return;}else{t.push(left / right);break;}default:break;}}else{t.push(stoi(str));}}return t.top();}
};

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

  • 建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行。
  • 当运算类型增加时,就只需要建立新增运算符与其对应函数之间的映射关系即可。
class Solution
{
public:int evalRPN(vector<string>& tokens){// 定义一个栈stack<int> t;unordered_map<string, function<int(int, int)>> op ={{"+", [](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;int right;if (str == "+" || str == "-" || str == "*" || str == "/"){left = t.top();t.pop();right = t.top();t.pop();t.push(op[str](left, right));}else{t.push(stoi(str));}}return t.top();}
};

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

5、function包装器的意义

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

二、bind包装器

1、bind包装器介绍

(1)bind包装器

bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,C++中的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...:要绑定的参数列表:值或占位符。

(2)调用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类型指明返回值和形参类型后接收包装后的可调用对象。

2、bind包装器绑定固定参数

(1)无意义的绑定

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

在这里插入图片描述

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

(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);std::cout << func(2) << std::endl;return 0;
}

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

3、bind包装器调整传参顺序

(1)调整传参顺序

对于下面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);std::cout << func(1, 2) << std::endl;return 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);std::cout << func(1, 2) << std::endl;return 0;
}

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

(2)bind包装器的意义

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

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

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

相关文章

vite和vue-cli实现原理和优化及区别

Vite&#xff1a; 1. 实现原理&#xff1a; Vite 是一个基于 ESModule 的构建工具。它利用原生 ESModule 的特性&#xff0c;将每个文件作为一个模块&#xff0c;通过浏览器去解析和执行&#xff0c;而不需要提前将文件打包成一个单独的 bundle。Vite 利用浏览器的原生 ESMod…

LeetCode 热题 100 | 链表(中上)

目录 1 141. 环形链表 1.1 哈希表 1.2 快慢指针 2 142. 环形链表 II 2.1 哈希表 2.2 快慢指针 3 21. 合并两个有序链表 4 2. 两数相加 菜鸟做题第三周&#xff0c;语言是 C 1 141. 环形链表 1.1 哈希表 解题思路&#xff1a;遍历链表&#xff0c;在哈希表中…

Linux下grep命令详解

grep #文件内容过滤显示 #在指定的普通文件中查找并显示含有指定字符串的行&#xff0c;也可与管道符一起使用格式&#xff1a; grep-参数 查找条件 文件名 参数&#xff1a; 示例&#xff1a; [rootnode1 ~]# grep -n "root" /etc/passwd # -n&a…

Vue3学习记录(一)--- 组合式API之基础概念和变量声明

一、组合式API基础 1、简介 ​ 组合式 API (Composition API) 是Vue3和Vue2的v2.7之后版本中的全新特性&#xff0c;是一系列API的的集合&#xff08;响应式API、生命周期钩子、依赖注入等等&#xff09;&#xff0c;其风格是基于函数的组合&#xff0c;以一种更直观、更灵活…

【Unity3D小技巧】Unity3D中UI控制解决方案

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中总是会控制UI界面&#xff0c;如何优雅的控制UI界面是…

02-Java抽象工厂模式 ( Abstract Factory Pattern )

抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是围绕一个超级工厂创建其他工厂 该超级工厂又称为其他工厂的工厂 在抽象工厂模式中&#xff0c;接口是负责创建一个相关对象的工厂&#xff0c;不需要显式指定它们的类 每个生成的工厂都能按照工厂模式提供对象 …

CDS view与替代对象

一&#xff0c;简介 替代对象是指用一个CDS view指派给一个透明表或常规数据库视图&#xff0c;使得透明表或常规数据库视图的访问重定向到该CDS view。 替代有诸多要求&#xff1a; 字段数量一致且同名对应&#xff0c;顺序可以不一致对应的字段数据类型长度等必须一致CDS v…

在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(三)开发和发布自己开发的maven插件

系列文章目录 在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(二)发布自己开发的jar包 文章目录 系列文章目录在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(二)发布自己开发的jar包 前言一、插件需求二、maven自定…

前端JavaScript篇之对 rest 参数的理解、ES6中模板语法与字符串处理

目录 对 rest 参数的理解ES6中模板语法与字符串处理 对 rest 参数的理解 rest参数是一种在函数定义中使用的特殊语法&#xff0c;它允许函数接受任意数量的参数&#xff0c;并将它们收集到一个数组中。通俗地说&#xff0c;rest参数就像是一个容器&#xff0c;用来存放函数接收…

帮管客CRM SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

2024年美赛B题:寻找潜水器 Searching for Submersibles 思路模型代码解析

2024年美赛B题&#xff1a;寻找潜水器 Searching for Submersibles 思路模型代码解析 【点击最下方群名片&#xff0c;加入群聊&#xff0c;获取更多思路与代码哦~】 问题翻译 海上游轮迷你潜艇&#xff08;MCMS&#xff09;是一家位于希腊的公司&#xff0c;专门制造能够将人…

如何在Windows部署GoLand并通过SSH远程连接Linux服务器

文章目录 1. 安装配置GoLand2. 服务器开启SSH服务3. GoLand本地服务器远程连接测试4. 安装cpolar内网穿透远程访问服务器端4.1 服务器端安装cpolar4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 本文主要介绍使用GoLand通过SSH远程连接服务器&#xff0c;并结合cpolar内…

Vue 环境准备

1.安装vscode https://code.visualstudio.com/ 2.安装开发vue所需插件&#xff1a; Vetur —— 语法高亮、智能感知、Emmet等 包含格式化功能&#xff0c; AltShiftF &#xff08;格式化全文&#xff09;&#xff0c;CtrlK CtrlF&#xff08;格式化选中 代码&#xff0c;两…

在 Elastic Agent 中为 Logstash 输出配置 SSL/TLS

要将数据从 Elastic Agent 安全地发送到 Logstash&#xff0c;你需要配置传输层安全性 (TLS)。 使用 TLS 可确保你的 Elastic Agent 将加密数据发送到受信任的 Logstash 服务器&#xff0c;并且你的 Logstash 服务器从受信任的 Elastic Agent 客户端接收数据。 先决条件 确保你…

防御保护 防火墙的双机热备和带宽管理实验

需求&#xff1a; 办公区设备可以通过电信和移动链路上网&#xff08;多对多的NAT&#xff0c;并需要保留一个公网IP不能用来转换&#xff09;分公司设备可以通过总公司的移动和电信链路访问DMZ区的http服务器分公司内部的客户端可以通过公网地址访问到内部的服务器FW1和FW4组…

Unity | YooAssetV2.1.0 + HybridCLR热更新

目录 一、项目更改 二、使用YooAsset热更 1.资源配置 2.资源构建 3.将两个文件夹下的资源上传CDN服务器 4.修改代码 5.运行效果 本文记录利用YooAssetHybridCLR来进行资源和dll的更新。YooAsset使用的是新版V2.1.0。相比于旧版&#xff0c;dll(原生文件)和资源要建两个p…

AI-数学-高中-18-三角函数-同角三角函数关系及计算

原作者视频&#xff1a;三角函数】5同角三角函数关系&#xff08;易中档&#xff09;_哔哩哔哩_bilibili 辅助三角形&#xff08;计算速度快&#xff09;&#xff1a;1.画一个辅助计算的任意直接三角形&#xff1b;2.利用初中方法先计算sin、cos、tan值&#xff1b;3.看象限确定…

Python机器学习:样本划分之验证集法

评估机器学习模型优劣的标准是模型的泛化能力&#xff0c;所以关注的应该是泛化误差而不是经验误差&#xff0c;一味追求经验误差的降低&#xff0c;就会导致模型“过拟合”现象。但是泛化误差是难以直接观察到的&#xff0c;我们应该如何选择泛化能力比较好的模型呢&#xff1…

蓝桥杯---垒骰子

赌圣atm晚年迷恋上了垒骰子&#xff0c;就是把骰子一个垒在另一个上边&#xff0c;不能歪歪扭扭&#xff0c;要垒成方柱体。经过长期观察&#xff0c;atm 发现了稳定骰子的奥秘&#xff1a;有些数字的面贴着会互相排斥&#xff01;我们先来规范一下骰子&#xff1a;1的对面是4&…

【Docker篇】Linux安装Docker、docker安装mysql、redis、rabbitmq

1.Linux安装docker 官方帮助文档&#xff1a;Install Docker Engine on CentOS | Docker Docs 1.1安装命令 # 1. 卸载之前的dockersudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate…