function包装器和bind包装器

news2024/11/29 4:38:29

function包装器和bind包装器

  • 包装器
  • function包装器
    • 为什么需要function
    • function包装器
    • function包装器的应用场景
      • 逆波兰表达式求值
  • bind包装器
    • bind包装器的应用场景

包装器

包装器是用于给其他编程接口提供更一致或更合适的接口

由于函数调用可以使用函数名、函数指针、函数对象和lambda表达式,可调用类型太丰富导致模板的效率极低。包装器用于解决效率低的问题

function包装器

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

为什么需要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);
}

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

useF中的f(x),f可能是什么呢?函数名?函数指针?仿函数对象?lambda表达式对象?这些都是可调用的类型对象,这就会导致模板的效率低下。怎么个低下法呢?我们执行上述代码,得到结果:在这里插入图片描述

可以看到,上述代码产生了三个不同的静态变量count,说明useF函数模板被实例化出了三份。之所以被实例化出三份,是因为fFunctor()[](double d)->double { return d / 4; }的类型并不相同。

那么为了提高效率,能不能让useF函数模板只实例化一份呢?换句话说,能不能让这三个类型都统一成一个类型?–function包装器

function包装器

std::function本质是个类模板,在头文件<functional>

template <class Ret, class... Args>
class function<Ret(Args...)>;		//Args...是参数列表
  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

根据上面的例子,被调用的函数是double f(double i),因此Ret和Args都是double

所以void test1()中对于useF的调用可以写成这样:

void test1()
{
	// 函数名
	function<double(double)> f1 = f;
	// 仿函数对象
	function<double(double)> f2 = Functor();
	// lamber表达式对象
	function<double(double)> f3 = [](double d)->double { return d /4; };

	cout << useF(f1, 11.11) << endl;
	cout << useF(f2, 11.11) << endl;
	cout << useF(f3, 11.11) << endl;
}

运行结果如下:在这里插入图片描述

可以看到只生成了一个静态变量count,说明只实例化出了一份函数。这样就提高了效率

function包装器的应用场景

  • 场景一:上面所讲的提高模板的使用效率
  • 场景二:将可调用对象的类型统一

针对场景二,进行详细讲解:

为什么要将这些可调用对象的类型进行统一呢?因为统一后,可以将不同的可调用对象都存储在同一个容器中,方便管理

以前想把那些可调用对象存在同一个vector中是几乎不可能的,现在借助包装器,将可调用对象类型统一后,就可以存储了

比如,延续上面的例子:

	// 函数名
	function<double(double)> f1 = f;
	// 仿函数对象
	function<double(double)> f2 = Functor();
	// lamber表达式对象
	function<double(double)> f3 = [](double d)->double { return d / 4; };

	//写法一:
	vector<function<double(double)>> v = { f1,f2,f3 };

	//写法二:直接写:
	vector<function<double(double)>> v = { f,Functor(),[](double d)->double { return d / 4; } };

下面给出一个实例:

逆波兰表达式求值

150. 逆波兰表达式求值 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述

  • 以前的做法:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        //将tokens中的数字依次入栈,若遇到操作符则出栈计算
        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();
    }
  • 现在利用function包装器实现:

这里是一个命令对应一个动作,比如遇到”+“,就实现两个数相加。

(凡是类似这种场景,都可以利用包装器。就比如说linux的命令,就可以这样实现)

    int evalRPN(vector<string>& tokens) 
    {
        map<string,function<int(int,int)>> m ={
            {"+",[](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;}}
        };

        stack<int> st;
        //将tokens中的数字依次入栈,若遇到操作符则出栈计算
        for(auto& str:tokens)
        {
            if(m.count(str))
            {
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
                st.push(m[str](left,right));
            }
            else
            {
                st.push(stoi(str));//将字符串变成整型存入栈中
            }
        }

        return st.top();
    }

上述代码还有一个优势,就是如果再增加运算符操作的话,只需在map这增加代码即可,其他地方不需要改动

这里为了简便就用了lambda表达式。当然也可以写函数名、仿函数对象,如下:

class Solution 
{
public:
    static int add(int x, int y)
    {
        return x+y;
    }

    struct sub
    {
        int operator()(int x,int y)
        {
            return x-y;
        }
    };

    int evalRPN(vector<string>& tokens) 
    {
        map<string,function<int(int,int)>> m ={
            {"+",add}
            ,{"-",sub()}
            ,{"*",[](int x,int y){return x*y;}}
            ,{"/",[](int x,int y){return x/y;}}
        };

	//……
    }
};

int add(int x, int y)前面要加一个static是因为:这个函数是在类中,类成员函数一般都是由对象去调用的,这里只想要用它的地址 加上静态关系后,就不需要依靠对象调用了,光写函数名就能代表函数的地址了

bind包装器

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

bind包装器的应用场景

  1. 调整函数的参数顺序

我们看如下代码:

int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	function<int(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2);
	cout << rSub1(10, 5) << endl;

	function<int(int, int)> rSub2 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << rSub2(10, 5) << endl;
	return 0;
}

这里的placeholders是个命名空间,_1_2都是命名空间中的标识符

运行结果:在这里插入图片描述

我们看改变的地方只有一处:在这里插入图片描述

这就是利用bind实现的参数顺序的调换

我们理一下调用逻辑:

  • 对于rSub1:

    在这里插入图片描述

  • 对于rSub2:

    在这里插入图片描述

总结:

  • bind过后的函数传参时:第一个实参只会传给标识符_1,第二个实参只会传给标识符_2,以此类推

    这里传几个实参,bind语句中就有几个_,且是对应死的。第一个实参就对应于_1

    所以说,在上面写个_3是会报错的

  • bind传给原函数时:不再看什么标识符,按照位置关系进行传参

  1. 调整函数的传参个数(也就是将原本函数中的某一个参数绑定死)

看如下代码:

double Plus(int x, int y, double rate)
{
	return (x + y) * rate;
}

int main()
{
	function<double(int, int)> plus1 = bind(Plus, placeholders::_1, placeholders::_2, 4.1);
	function<double(int, int)> plus2 = bind(Plus, placeholders::_1, placeholders::_2, 4.3);
	function<double(int, int)> plus3 = bind(Plus, placeholders::_1, placeholders::_2, 4.5);

	cout << plus1(5, 3) << endl;
	cout << plus2(5, 3) << endl;
	cout << plus3(5, 3) << endl;

	return 0;
}

这里就是将Plus函数的第三个参数rate固定

尽管缺省参数也可以实现将参数固定,但缺省参数只能固定一种情况,bind可以固定任意情况

此外,还要注意在写类型时,被绑定的参数的类型就不需要写在function中了

假如我最初函数写成这样:

在这里插入图片描述

那么bind语句就这样写:

在这里插入图片描述

注意这里还是_1和_2。因为这个是和bind后的函数的第几个实参对应的。

  1. 绑定类的成员函数

看如下代码:

class SubType
{
public:
	int sub(int x, int y)
	{
		return x - y;
	}

	static  int ssub(int a, int b, int rate)
	{
		return (a - b) * rate;
	}
};

int main()
{
	//bind静态成员函数
	function<int(int, int)> rssub = bind(SubType::ssub, placeholders::_1, placeholders::_2,3);
	cout << rssub(5, 3) << endl;

	//bind成员函数
	function<int(int, int)> rsub = bind(&SubType::sub,SubType(), placeholders::_1, placeholders::_2);
	cout << rsub(5, 3) << endl;

	return 0;
}

注意点:

  • 绑定类成员函数时,一定要声明这个函数在哪个类中,如上SubType::
  • bind成员函数时,需要在函数名前面加上&(静态成员函数可加可不加)
  • bind成员函数时,需要增加一个参数:类对象或者类对象的指针

其实,绑定的底层也是仿函数

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

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

相关文章

34.LengthFieldBasedFrameDecoder代码使用

public class TestLengthFieldDecorder {public static void main(String[] args) {//这里相当于服务端接收数据EmbeddedChannel embeddedChannel = new EmbeddedChannel(new LengthFieldBasedFrameDecoder(1024, 0,4, 0, 0),new LoggingHandler(LogLevel.DEBUG));//这里相当于…

Idea安装插件刷Leetcode

一、下载插件 idea版本2022以下两个插件都可以用来刷Leetcode 二、登录Leetcode 获取token 登录 三、配置 配置1 配置2 Code FileName C$!velocityTool.leftPadZeros($!{question.frontendQuestionId},4)$!velocityTool.replace(${question.title}," ","&qu…

【实战】Spring Cloud Stream 3.1+整合Kafka

文章目录 前言新版版本优势实战演示增加maven依赖增加applicaiton.yaml配置新增Kafka通道消费者新增发送消息的接口 实战测试postman发送一个正常的消息postman发送异常消息 前言 之前我们已经整合过Spring Cloud Stream 3.0版本与Kafka、RabbitMQ中间件&#xff0c;简直不要太…

【深度学习】实现基于MNIST数据集的TensorFlow/Keras深度学习案例

基于TensorFlow/Keras的深度学习案例 实现基于MNIST数据集的TensorFlow/Keras深度学习案例0. 什么是深度学习&#xff1f;1. TensorFlow简介2. Keras简介3. 安装TensorFlow前的注意事项4. 安装Anaconda3及搭建TensorFlow环境1&#xff09; 下载安装Anaconda Navigator2&#xf…

使用ESP32和Flask框架实现温湿度数据监测系统

项目概述 在这个项目中&#xff0c;我们将使用ESP32微控制器读取温湿度传感器的数据&#xff0c;并将这些数据通过HTTP请求传输到基于Flask框架的服务器。Flask是一个轻量级的Python Web框架&#xff0c;非常适合快速开发和部署Web应用。通过这个项目&#xff0c;我们不仅可以了…

36 - 按分类统计薪水(高频 SQL 50 题基础版)

36 - 按分类统计薪水 -- 方法一 selectLow Salary category,sum(income <20000) accounts_count fromAccounts union selectAverage Salary category,sum(income between 20000 and 50000) accounts_count fromAccounts union selectHigh Salary category,sum(in…

Linux htop命令使用

文章目录 简介界面介绍第一行第二行第三行第四行 如何使用 简介 htop 是一个类似于 top 的命令&#xff0c;但具有更丰富的功能和更友好的界面。它可以实时显示系统中各个进程的资源占用情况&#xff0c;如 CPU 使用率、内存使用率等。以下是对 htop 命令的完全解析&#xff1…

CANoe CAPL如何模拟发送CAN错误帧?

目录 canOutputErrorFrame介绍代码output(errorframe)代码总结canOutputErrorFrame 介绍 代码 canOutputErrorFrame(errorFrame, 12, 0); //output Error Frame with 12 dominant bits on CAN1 canOutputErrorFrame(CAN2.errorFrame, 6,

物理层(一)

第2章 物理层 2.1 通信基础 2.1.1 基本概念 1、数据、信号与码元 通信的目的是传输信息&#xff0c;如文字、图像和视频等。数据是指传送信息的实体。信号则是数据的电气或电磁表现&#xff0c;是数据在传输过程中的存在形式。数据和信号都有模拟或数字之分:①模拟数据(或模…

一个电商创业者眼中的618:平台大变局

战役结束了&#xff0c;战斗还在继续。 一位朋友去年5月创业&#xff0c;网上卖咖啡&#xff0c;这个赛道很拥挤&#xff0c;时机也不好&#xff0c;今年是他参加第一个618。朋友说&#xff0c;今年的目标是锤炼团队&#xff0c;总结方法&#xff0c;以及最重要的——活下去。…

设计模式——设计模式原则

设计模式 设计模式示例代码库地址&#xff1a; https://gitee.com/Jasonpupil/designPatterns 设计模式原则 单一职责原则&#xff08;SPS&#xff09;&#xff1a; 又称单一功能原则&#xff0c;面向对象五个基本原则&#xff08;SOLID&#xff09;之一 原则定义&#xf…

PHP环境搭建之使用PhpStudy

文章目录 1 PhpStudy1.1 简介1.2 下载&安装1.3 修改配置1.3.1 Apache配置1.3.2 MySQL配置1.3.3 MySQL启动问题 1.4 Composer1.4.1 简介1.4.2 下载安装1.4.3 修改配置1.4.4 使用命令 1 PhpStudy 1.1 简介 phpstudy是一个php运行环境的集成包&#xff0c;用户不需要去配置运…

2024/06/21--代码随想录算法10-12/17| 子序列问题

300.最长递增子序列 力扣链接 动规五部曲 dp的定义 dp[i]表示子序列答案以nums[i]结尾的最长递增子序列的长度 为什么一定表示 “以nums[i]结尾的最长递增子序” &#xff0c;因为我们在 做 递增比较的时候&#xff0c;如果比较 nums[j] 和 nums[i] 的大小&#xff0c;那么两…

怎么采集阿里巴巴1688的商品或商家数据?

怎么使用简数采集器批量采集阿里巴巴1688的商品或商家相关信息呢&#xff1f; 简数采集器暂时不支持采集阿里巴巴1688的相关数据&#xff0c;谢谢。 简数采集器采集网络网页数据非常简单高效&#xff1a;输入要采集的网址&#xff0c;简数智能算法会自动提取出网页上的关键信…

windows端口被占用问题,杀死进程

描述&#xff1a;端口被占用 在使用IntelliJ IDEA运行程序时&#xff0c;可能会遇到端口占用的情况&#xff0c;这通常由以下几个原因引起&#xff1a; 1、同一程序多次启动&#xff1a;如果你没有正确关闭之前运行的程序实例&#xff0c;再次尝试运行相同的程序时&#xff0c;…

前端实现对本地文件的IO操作

前言 在网页中&#xff0c;前端已经可以读取本地文件系统&#xff0c;对本地的文件进行IO读写&#xff0c;甚至可以制作一个简单的VScode编辑器。这篇文章以渐进式方式实现此功能&#xff0c;文末附上所有代码。 首先看整体功能演示 功能概述 我们将实现一个简单的 Web 应…

全面国产化信创适配改造方案说明

一、概叙 系统的全面国产化适配改造需要从多个方面进行考虑&#xff0c;改造前需要进行充分的论证&#xff0c;在满足具体业务场景的前提下&#xff0c;以确保系统的稳定性和安全性&#xff0c;同时还要考虑技术的发展&#xff0c;不断优化和更新。因此全面国产化适配改造也面临…

【React】富文本编辑器react-quill

安装 react-quill 富文本编辑器 npm i react-quill2.0.0-beta.2报错解决&#xff1a; npm i react-quill2.0.0-beta.2 --legacy-peer-deps导入编辑器组件和配套样式文件 import ReactQuill from react-quill // 1 import react-quill/dist/quill.snow.css // 2const Publi…

C++:STL容器-map

C:STL容器-map 1. map构造和赋值2. map大小和交换3. map插入和删除4. map查找和统计5. map容器排序 map中所有元素都是pair&#xff08;对组&#xff09; pair中第一个元素为key&#xff08;键&#xff09;&#xff0c;起到索引作用&#xff0c;第二个元素为value&#xff08;实…

开发指南033-数据库兼容

元芳&#xff0c;你怎么看&#xff1f; 单一数据库自身就有一些不同处理之处&#xff0c;如果一个平台要兼容所有数据库&#xff0c;就是难上加难&#xff0c;像isnull函数各数据库就不同。 对于这类问题&#xff0c;平台采用统一自定义函数解决&#xff0c;例如上面的round函…