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

news2024/11/18 5:41:39

文章目录

  • 1. function包装器
    • 1.1 遇到的问题
    • 1.2 包装器的定义
    • 1.3 解决问题
    • 1.4 包装器的其他应用
  • 2. bind
    • 2.1 bind的定义
    • 2.2 bind包装器绑定固定参数
    • 2.3 bind包装器调整传参顺序
    • 2.4 bind包装器的意义

1. function包装器

1.1 遇到的问题

我们首先来看一行代码:

ret = func(x);

假设这行代码能够正常运行,那么这个func是什么呢?函数名? 函数指针? 函数对象? lambda表达式对象?很多种可能性,这些都是可调用的类型。这么多的类型可能会导致模板的效率低下

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;
	}
};
void Test1()
{
	cout << useF(f, 11.11) << endl;//函数名
	
	cout << useF(Functor(), 11.11) << endl;//函数对象
	
	cout << useF([](double d)->double {return d / 4; }, 11.11) << endl;//lambda表达式
}

image-20230727094927877

可以看到这里的useF函数模板被实例化了三份。但是使用function包装器就可以让只被实例化一份出来。


1.2 包装器的定义

function包装器,也叫做适配器。C++中的function本质是一个类模板

image-20230727092701982

模板参数说明:

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

如何使用包装器呢?

int func(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;
	}
	int plusd(int a, int b)
	{
		return a + b;
	}
};
void Test2()
{
	//函数名(函数指针)
	function<int(int, int)> func1 = func;
	cout << func1(1, 2) << endl;
	//函数对象(仿函数)
	function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	//lambda表达式
	function<int(int, int)> func3 = [](const int a, const int b)->int {return a + b; };
	cout << func3(1, 2) << endl;
	//类的静态成员函数
	function<int(int, int)> func4 = &Plus::plusi;//这里可以不加取地址符号
	cout << func4(1, 2) << endl;
	//类的成员函数
	function<int(Plus, int, int)> func5 = &Plus::plusd;//这里必须要加取地址符号,并且声明的时候需要显示的传类名
	cout << func5(Plus(), 1, 2) << endl;//调用的时候需要传对象
}

image-20230727101249743

1.3 解决问题

现在我们知道了包装器的用法了,那么怎么解决最开始的问题呢?

使用包装器包装可调用对象,然后将可调用对象传给useF

void Test3()
{
	function<double(double)> func1 = f;
	function<double(double)> func2 = Functor1();
	function<double(double)> func3 = [](double d)->double {return d / 4; };

	cout << useF(func1, 11.11) << endl;
	cout << useF(func2, 11.11) << endl;
	cout << useF(func3, 11.11) << endl;
}

image-20230727103219920

可以看到,这里的useF函数模板只实例化出了一个对象。

1.4 包装器的其他应用

我们之前做过一道题:逆波兰表达式求解150. 逆波兰表达式求值 - 力扣(LeetCode),当时使用的方法代码如下

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto& str : tokens)
        {
            if(str == "+" || str == "-" || str == "*" || str == "/")
            {
                int right = st.top();
                st.pop();
                int 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;
                }
            }
            else//遇到数字
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

在这里我们使用switch语句来判断运算类型,还需要在前面的条件里面枚举出运算类型,非常麻烦,如果在工程中,这段代码的可维护性就非常差,需要在if语句中增加枚举,在switch语句中增加case语句,这种情况就可以使用包装器来简化代码

  • 首先建立运算符和执行函数之间的映射关系,当需要某一运算的时候就直接通过运算符找到对应的可调用对象调用即可。、
  • 当运算类型增加时,只需要增加运算符和执行函数之间的映射关系即可

那么修改后的代码如下

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string, function<int(int, int)>> opFuncMap = 
        {
            {"+", [](int x, int y){return x + y;}},
            {"-", [](int x, int y){return x - y;}},
            {"*", [](int x, int y){return x * y;}},
            {"/", [](int x, int y){return x / y;}}
        };
        for(auto& str : tokens)
        {
            if(opFuncMap.count(str) == 0)
            {
                st.push(stoi(str));
            }
            else
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
        }
        return st.top();
    }
};

2. bind

2.1 bind的定义

**bind时一个函数模板,就像是一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来”适应“原对象的参数列表。**一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用bind函数还可以实现参数顺序调整等操作。

image-20230727140931890

参数列表说明:

  • Fn:可调用对象
  • Ret:可调用对象的返回类型
  • Args:要绑定的参数列表:值或者时占位符

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。调用bind的一般形式:auto newCallable = bind(callable,arg_list)

  • newCallable:生成的新的可调用对象

  • callable:需要包装的可调用对象

  • arg_list:逗号分隔的参数列表,对应给定的callable的参数,当调用newCallable的时候,newCallable会调用callable,并传给他arg_list中的参数。

一般使用bind调整参数位置的时候,会使用一个类placeholders,这是一个占位符类。

image-20230727142425111

表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。

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

2.2 bind包装器绑定固定参数

首先我们来看一种无意义的绑定

int Sub(int a, int b)
{
	return a - b;
}
void Test4()
{
	function<int(int, int)> func1 = bind(Sub, placeholders::_1, placeholders::_2);
	cout << func1(1, 3) << " " << Sub(1, 3) << endl;
}

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

如果想让Sub的第二个参数固定绑定为10,就可以将绑定时的参数列表的palceholder::_2设置为10.

int Sub(int a, int b)
{
	return a - b;
}
void Test4()
{
	function<int(int)> func2 = bind(Sub, placeholders::_1, 10);
	cout << func2(2) << endl;
}

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

2.3 bind包装器调整传参顺序

同样的对于上面的Sub函数的例子,我们想让传入的参数顺序进行调换,也可以使用bind实现。将placeholder::_1和placeholder::_2的位置进行调换即可

void Test5()
{
	function<int(int, int)> func = bind(Sub, placeholders::_2, placeholders::_1);
	cout << func(1, 2) << endl;
}

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

2.4 bind包装器的意义

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

本节完…

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

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

相关文章

Kafka 入门到起飞系列 - 消费者组管理、位移管理

消费者组 - Consumer Group 上文我们已经讲过消费者组了&#xff0c;我们知道消费组的存在可以保证一个主题下一个分区的消息只会被组内一个消费者消费&#xff0c;从而避免了消息的重复消费 什么是消费组 - Consumer Group&#xff1f; 消费者组是Kafka 提供的可扩展且具有容…

Vue教程(二):数据代理和事件处理

1、数据代理 数据代理 通过obj2操作obj1的对象 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…

【图论】三种中心性 —— 特征向量、katz 和 PageRank

维基百科&#xff1a;在图论和网络分析中&#xff0c;中心性指标为图中相应网络位置的节点分配排名或数值。中心性这一概念最初起源于社交网络分析&#xff0c;因此很多衡量中心性的术语也反映了其社会学背景。 不同中心性指标对 “重要” 的衡量方式不同&#xff0c;因此适用于…

Android调用摄像头拍照从相册中选择图片

以下内容摘自郭霖《第一行代码》第三版 activity_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-a…

简单分享婚宴预订小程序怎么做

婚宴预订小程序需要具备一些功能&#xff0c;通过这些功能&#xff0c;新人可以更方便地选择婚宴场地、预订服务&#xff0c;并且更好地规划自己的婚礼。 1. 场地浏览与选择 婚宴预订小程序可以展示多个婚宴场地的照片和详细信息&#xff0c;包括容纳人数、场地设施、价格等。…

Flutter:flutter_local_notifications——消息推送的学习

前言 注&#xff1a; 刚开始学习&#xff0c;如果某些案例使用时遇到问题&#xff0c;可以自行百度、查看官方案例、官方github。 简介 Flutter Local Notifications是一个用于在Flutter应用程序中显示本地通知的插件。它提供了一个简单而强大的方法来在设备上发送通知&#…

PHP 3des加解密新旧方法可对接加密

一、旧3des加解密方法 <?php class Encrypt_3DES {//加密秘钥&#xff0c;private $_key;private $_iv;public function __construct($key, $iv){$this->_key $key;$this->_iv $iv;}/*** 对字符串进行3DES加密* param string 要加密的字符串* return mixed 加密成…

blender 用蒙版添加材质

一、添加材质常规方法 选择物体新建材质&#xff0c;shift a 新建图像纹理&#xff0c;此时会发现添加上的纹理会有接缝&#xff0c;shift a 新建映射 纹理坐标&#xff0c;纹理坐标选择生成&#xff0c;此时&#xff0c;之前的接缝便会消失&#xff1b; 如何快捷添加纹理坐…

【应用】Asible自动化运维工具的应用与常用命令

ansible自动化运维工具 一、ansible 的概述1. ansible 的概念2. ansible 的特性 二、ansible 的部署与命令1. ansible 的部署1.1 服务器ip地址设置1.2 ansible 服务器部署 2. ansible 命令行模块2.1 command 模块2.2 shell 模块2.3 cron 模块2.4 user 模块2.5 group 模块2.6 co…

TCP KeepAlive与HTTP Keep-Alive

TCP KeepAlive与HTTP Keep-Alive TCP KeepAliveHTTP Keep-AliveTCP服务器怎么检测客户端断开连接 TCP KeepAlive TCP连接建立之后&#xff0c;如果应用程序或者上层协议一直不发送数据&#xff0c;或者隔很长时间才发送一次数据&#xff0c;那么TCP需要判断是应用程序掉线了还…

postgresql|数据库|启动数据库时报错:FATAL: could not map anonymous shared memory的解决

前言&#xff1a; 一个很偶然的出现的问题&#xff0c;因为我需要验证备份文件是否正确&#xff0c;因此&#xff0c;我在一台已启动了一个数据库实例的服务器上&#xff0c;依据全备的数据库文件在启动一个实例&#xff0c;当然&#xff0c;在此之前&#xff0c;已经修改了备…

C语言习题练习

C语言习题练习 一、offsetof宏二、交换奇偶位三、原地移除数组总结 一、offsetof宏 首先我们要了解什么是offsetof宏&#xff1a; . 此具有函数形式的宏返回数据结构或联合类型中成员成员的偏移值&#xff08;以字节为单位&#xff09;。 . 返回的值是size_t类型的无符号整数…

DevOps(四)

CD(二) 1. CDStep 1 - 上传代码Step 2 - 下载代码Step 3 - 检查代码Step 4 - 编译代码Step 5 - 上传仓库Step 6 - 下载软件Step 7 - 制作镜像Step 8 - 上传镜像Step 9 - 部署服务2. 整体预览2.1 预览1. 修改代码2. 查看sonarqube检查结果3. 查看nexus仓库4. 查看harbor仓库5.…

【打卡】Datawhale暑期实训ML赛事

文章目录 赛题描述任务要求数据集介绍评估指标 赛题分析基于LightGBM模型Baseline详解改进baseline早停法添加特征 赛题描述 赛事地址&#xff1a;科大讯飞锂离子电池生产参数调控及生产温度预测挑战赛 任务要求 初赛任务&#xff1a;初赛提供了电炉17个温区的实际生产数据&…

字典树Trie

Trie树又称字典树&#xff0c;前缀树。是一种可以高效查询前缀字符串的树&#xff0c;典型应用是用于统计&#xff0c;排序和保存大量的字符串&#xff08;但不仅限于字符串&#xff09;&#xff0c;所以经常被搜索引擎系统用于文本词频统计。 它的优点是&#xff1a;利用字符串…

欧姆龙 NJ SNMP 协议的使用,用于监控PLC的网络状态

NJ SNMP 协议的使用 实验时间&#xff1a;2023.07.25 实验器材&#xff1a;NJ501-1300 实验目的&#xff1a;NJ SNMP 协议的使用 1. SNMP 协议介绍 ​ SNMP&#xff08;Simple Network Management Protocol&#xff09;是一种简单网络管理协议。它属于 TCP/IP 五层协议中的…

Cerbero Suite Advanced Crack

Cerbero Suite Advanced Crack 用于软件分类和文件分析的最先进的工具套件。分析多种文件格式&#xff0c;包括PE、Mach-O、ELF、Java、SWF、DEX、PDF、DOC、XLS、RTF、Zip等。 它提供自动分析、交互式分析、Carbon interactive Disassembler、字节码反汇编程序、带布局的十六进…

一篇文章搞定《EventBus详解》

一篇文章搞定《EventBus详解》 前言EventBus简述EventBus的使用EventBus源码解析初始化并构建实例EventBus.getDefault()EventBus.builder()EventBus初始化的重要成员 注册流程register方法SubscriberMethodFinder类findSubscriberMethods方法findUsingReflection方法&#xff…

掌握Python的X篇_10+11_if分支语句、else语句、elif语句

文章目录 1. if关键字及语法2. 语句块的概念3. else语句4. elif语句 1. if关键字及语法 基本语法如下&#xff1a; if 条件表达式:条件为True时&#xff0c;要执行的语句举例&#xff1a; number int(input("Input an number")) if number > 5 :print("这…

【Spring框架】spring对象注入的三种方法

目录 1.属性注入问题&#xff1a;同类型的Bean存储到容器多个&#xff0c;获取时报错的问题&#xff1b;1.将属性的名字和Bean的名字对应上。2.使用AutoWiredQualifier来筛选bean对象&#xff1b; 属性注入优缺点 2.Setter注入Setter注入优缺点 3.构造方法注入&#xff08;Spri…