【C++】包装器-bind function

news2024/11/8 19:58:27

文章目录

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

包装器

function包装器

function包装器介绍

function包装器

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

function类模板的原型如下:

template <class T> function;     // undefined 报错!缺少类模板function的参数模板
//正确写法:
template <class Ret, class... Args>
class function<Ret(Args...)>;

参数说明:

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


包装示例

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

#include<functional>
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、包装函数指针(函数名)
    //要包装f:返回值是int,参数有两个都是int,function<int(int, int)> 包装器的类型
	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、类的非静态成员函数
    //非静态的成员函数,需要多增加一个类型,因为需要对象去调用,第一个参数是隐藏this指针
	function<double(Plus, double, double)> func5=&Plus::plusd;//普通的成员函数取地址必须要加&
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

注意1:包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以像普通函数一样使用了

2.取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”

3.包装非静态的成员函数时需要注意:非静态成员函数的第一个参数是隐藏this指针,因此在包装时需要指明第一个形参的类型为类的类型


function包装器统一类型

包装器可以对不同类型的东西包装成统一的类型格式

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);
}

对于上述函数模板useF:

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

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

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;
}

image-20221018085809995

由于函数指针、仿函数、lambda表达式是不同的类型,因此useF函数会被实例化出三份,三次调用useF函数所打印count的地址也是不同的, 但实际这里根本没有必要实例化出三份useF函数,因为三次调用useF函数时传入的可调用对象虽然是不同类型的.但这三个可调用对象的返回值和形参类型都是相同的,那如何解决呢?

  • 这时就可以用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数.这时就只会实例化出一份useF函数
  • 根本原因就是因为包装后,这三个可调用对象都是相同的function类型.因此最终只会实例化出一份useF函数.该函数的第一个模板参数的类型就是function类型的
int main()
{
	//函数名
	function<double(double)> func1 = f;
	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;
}

image-20221018090031251

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


function包装器简化代码的列子

求解逆波兰表达式的步骤如下:

  • 定义一个栈.依次遍历所给字符串.
  • 如果遍历到的字符串是数字则直接入栈.
  • 如果遍历到的字符串是加减乘除运算符.则从栈定抛出两个数字进行对应的运算.并将运算后得到的结果压入栈中.
  • 所给字符串遍历完毕后.栈顶的数字就是逆波兰表达式的计算结果
class Solution {
public:
    //得到两个操作数
    void  getNum(stack<int>&st,int& left,int& right)
    {
        right = st.top(); //先出的是右操作数
        st.pop();
        left = st.top();
        st.pop();
    }
    //tokens = ["2","1","+","3","*"]
    int evalRPN(vector<string>& tokens) {
        stack<int> st;//存放结果
        for(auto& str: tokens)//遍历字符串
        {
            int left,right;
            switch(str[0]) 
            {
                case '+':
                    getNum(st,left,right);//得到两个操作数
                    st.push(left+right);//把计算结果压到栈中
                    break;
                case '-':
                    //操作符
                    if(str.size() == 1)
                    {
                        getNum(st,left,right);//得到两个操作数
                        st.push(left-right);//把计算结果压到栈中
                        break; 
                    }
                    //操作数
                    else
                    {
                        st.push(stoi(str));//转为整数压到栈中
                        break;
                    }
                case '*':
                    getNum(st,left,right);//得到两个操作数//得到两个操作数
                    st.push(left*right);
                    break; 
                case '/':
                    getNum(st,left,right);//得到两个操作数
                    st.push(left/right);//把计算结果压到栈中
                    break; 
                default:
                    st.push(stoi(str));//转为整数压到栈中
                    break;
            }
        }
        return st.top();//返回栈顶数据就是最后的结果
    }
};

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

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

  • 建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行. 所以需要使用哈希表: unordered_map<string, function<int(int, int)>>
  • 当运算类型增加时.就只需要建立新增运算符与其对应函数之间的映射关系即可.

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

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //建立运算符与其对应需要执行的函数之间的映射关系
        unordered_map<string,function<int(int,int)>> opFuncMap;
        //插入键值对
        opFuncMap["+"] = [](int a, int b) ->int{return a+b;};
        opFuncMap["-"] = [](int a, int b) ->int{return a-b;};
        opFuncMap["*"] = [](int a, int b) ->int{return a*b;};
        opFuncMap["/"] = [](int a, int b) ->int{return a/b;};

        stack<int> st;
        for(size_t i = 0;i<tokens.size();i++)
        {
            string& str = tokens[i];
            //如果str为操作数:直接进栈
            if(opFuncMap.find(str) == opFuncMap.end())
            {
                //字符串->整数压入栈
                st.push(stoi(str));//st.push(atoi(str.c_str()))
            }
            else //str为操作符,取栈顶的两个元素进行运算
            {
                //注意:先拿到的是右操作数
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                //opFuncMap[str]得到的就是和str操作符对应的函数, 然后调用该函数进行计算
                st.push(opFuncMap[str](left,right));//把计算结果压入栈
            }
        }
        return st.top();//返回栈顶的元素,就说最终计算结果
    }
};

当然也可以这样初始化:

unordered_map<string, function<int(int, int)>> opFuncMap = {
    { "+", [](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; } }
};

如果还有其它符号,下面的代码是不需要改动的,只需要增加键值对!


注意:a*b可能存在溢出的情况,所以需要改为:

opFuncMap["*"] = [](int a, int b) ->int{return (long)a*b;}; 强转一下类型


function包装器的意义

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

bind包装器

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...:要绑定的参数列表:值或占位符.


调用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; //3
    cout << Plus(1, 2) << endl; //3
	return 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()
{
	//绑定固定参数,第二个参数固定绑定为10
	function<int(int)> func = bind(Plus, placeholders::_1, 10);
    //使用auto更加方便:
    //auto func = bind(Plus, placeholders::_1, 10);
	cout << func(2) << endl; //12
	return 0;
}

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


bind包装器调整传参顺序

调整传参顺序

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

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
    //绑定非静态成员函数,需要多增加一个类型参数
	//绑定固定参数 要绑定的函数是&Sub::sub, 将sub成员函数的第一个参数固定绑定为一个Sub对象-Sub()
	function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << func(1, 2) << endl; //-1
	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);
	cout << func(1, 2) << endl; //1
	return 0;
}

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


bind包装器的意义

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

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

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

相关文章

【Axure教程】通过输入框动态维护可视化图表

与静态图表相比&#xff0c;动态图表更能吸引观众的眼球并提供更好的视觉效果。动态元素可以吸引观众的注意力&#xff0c;使数据更生动、更具交互性。这有助于提高信息传达的效果&#xff0c;并能够引起观众的兴趣和参与。所以今天作者就教大家&#xff0c;如果通过输入框元件…

[NOIP2003 提高组] 加分二叉树

[NOIP2003 提高组] 加分二叉树 题目描述: 设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n)&#xff0c;其中数字 1,2,3,…,n 为节点编号。每个节点都有一个分数&#xff08;均为正整数&#xff09;&#xff0c;记第 i 个节点的分数为 di​&#xff0c;tree 及它的每个…

优雅草蜻蜓T系统·专业版服务端以及后台部署说明-完整步骤-语音会议室支持多人语音,屏幕分享,导航配置,会议管理,会员管理

蜻蜓T系统专业版服务端以及后台部署 1&#xff0c;解压文件和基础环境配置 将源码用git工具克隆到/www/wwwroot git clone git地址 或者是由优雅草发送的商业源码文件包直接进行解压 ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;…

十分钟柔和护理,轻松舒缓眼疲劳,云康宝眼部按摩仪体验

平时工作、生活中&#xff0c;每天都要长时间对着手机、电脑等电子设备&#xff0c;像是被它们吸走了灵魂&#xff0c;有时候眼睛干干的、痛痛的&#xff0c;像是被沙子刮过&#xff0c;光靠眼药水之类的东西根本解决不了问题&#xff0c;所以趁着618我入手了一款眼部按摩仪&am…

数字系统。网络层。IPv4 子网划分。ICMP

嘿&#xff0c;伙计们&#xff01;我希望你们一切都好。作为我每周更新计算机网络的一部分&#xff0c;我想分享一些令人兴奋的话题。 首先&#xff0c;我们深入研究了数字系统的世界。本主题重点介绍二进制和IPv4地址&#xff0c;我们学习了如何将二进制转换为十进制&#xf…

Zookeeper部署

Zookeeper的安装 环境变量的配置 上传安装包 使用MobaXterm、FinalShell或者使用scp将安装包apache-zookeeper-3.6.3-bin.tar.gz上传到/root/softwares下 复制代码 解压安装 [rootqianfeng01 ~]# tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz -C /usr/local 复制代码 更名 …

1091 Acute Stroke (PAT甲级)

这道题用dfs做的话&#xff0c;因为递归太多层&#xff0c;堆栈溢出&#xff0c;有两个测试点过不了&#xff1b;所以用bfs。 但令我百思不得其解的是&#xff0c;我没用方向变量x[6], y[6], z[6]&#xff0c;直接老老实实算每一个方向的话&#xff0c;最后一个测试点过不了&a…

17.6:迪瑞克斯啦算法

迪瑞克斯啦算法 这个算法研究的是&#xff1a;有向的&#xff0c;没有负权重&#xff0c;可以有环的图。 这个算法主要研究的是&#xff1a;给出的节点到这张图的其他节点的最短路径是多少。用一个表表示出来。 思路&#xff1a; 如下图所示&#xff0c;我们想要求出a节点到其…

建立时间、保持时间和亚稳态

目录 一、建立时间和保持时间 二、亚稳态 三、避免亚稳态策略 四、多级寄存器阻断亚稳态传播 一、建立时间和保持时间 如图1所示&#xff0c;建立时间&#xff08;set up time&#xff09;是指在触发器的时钟信号上升沿到来以前&#xff0c;数据稳定不变的时间&#xff0c;…

【Apache Pinot】探究 Pinot 中存储模型的设计逻辑和 Segment 详解

背景 上一篇文章中&#xff0c;笔者简单介绍了一下分布式数据库 Pinot 的核心组件&#xff0c;本文主要针对其中的存储模型会做部分讲解。 如果你对读写磁盘有不错的基础的话&#xff0c;看起来会更轻松一些&#xff0c;如果没有也没关系&#xff0c;我会简单讲解一下这么设计…

使用STM32进行串口实验(非中断+中断)

关于串口相关的基本知识可以看这篇文章https://blog.csdn.net/weixin_62599865/article/details/129963991?spm1001.2014.3001.5501 一.使用非中断的方式进行串口通信 串口发送/接收函数&#xff1a; HAL_UART_Transmit(); 串口发送数据&#xff0c;使用超时管理机制 HAL_…

2023最新版本Activiti7系列-Activiti7概述和入门案例

一、Activiti7概述 官网地址&#xff1a;https://www.activiti.org/ Activiti由Alfresco软件开发&#xff0c;目前最高版本Activiti 7。是BPMN的一个基于java的软件实现&#xff0c;不过Activiti 不仅仅包括BPMN&#xff0c;还有DMN决策表和CMMN Case管理引擎&#xff0c;并且有…

【前端 - HTML】第 1 课 - HTML 初体验

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 。 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、HTML 概念 2.1、HTML 定义 2.2、标签语法 3、HTML 基本骨架 4、标签的关系 5、注释 6、总结 1、缘起 最近在学习微信小程…

Apache Doris 冷热分层技术如何实现存储成本降低 70%?

在数据分析的实际场景中&#xff0c;冷热数据往往面临着不同的查询频次及响应速度要求。例如在电商订单场景中&#xff0c;用户经常访问近 6 个月的订单&#xff0c;时间较久远的订单访问次数非常少&#xff1b;在行为分析场景中&#xff0c;需支持近期流量数据的高频查询且时效…

C++ 使用一维数组和二维数组给 std::vector<cv::Point2d> 赋值的方法

文章目录 1. 一维数组给 vector 赋值的方法2. 一维 Point2d 数组给 vector<cv::Point2d> 赋值3. 二维 double 数组给 vector<cv::Point2d> 赋值 1. 一维数组给 vector 赋值的方法 &#xff08;1&#xff09;最简单的赋值方法是for循环遍历赋值&#xff0c;此处略过…

Python展开嵌套列表的五种方法

一、问题的提出 微信群中有人问&#xff0c;如何把以下内容转换成一个列表&#xff1a; 转换后&#xff1a; "[["007674","工银产业升级股票A","GYCYSJGPA","1.3574"],["007675","工银产业升级股票C",&qu…

d2l学习_第二章预备知识

x.1 Data Manipulation 数据操作。在Pytorch中常用的数据操作如下&#xff1a; 对于张量&#xff0c;即torch.Tensor类型的数据&#xff0c;你的任何操作都要把他想象成一个指针&#xff0c;因为等于运算符ab&#xff0c;会将b的张量内存地址赋值给a。 torch.Tensor类型的基…

day02-JavaScript-Vue

1 JavaScript html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 1.5.1.3 JSON对象 自定义对象 在 JavaScript 中自…

linux(信号发送后)

目录&#xff1a; 1.引入什么是合适的时候 2.内核态和用户态 3.信号的处理 4.sigaction函数 -------------------------------------------------------------------------------------------------------------------------------- 1.引入什么是合适的时候 2.信号什么时候被处…

你真的会PPT配色吗?来看看这篇吧,瞬间让你的PPT高大上起来

本文档使用技巧如下截图 在色彩里使用其它填充颜色 选取这个“吸管” 用于吸别人的颜色 我曾经为了出一个“惊艳”的PPT,光吸管用了不下150次。 好的艺术家复制,伟大的艺术家偷窃!--毕加索 下面就给出几大常用配色 各位在使用时注意看这些“色卡”的规律,那就是反差色…