当前位置: 首页 > news >正文

【C++11】std::function 包装器(又叫适配器),std::bind 绑定

文章目录

  • std::function 包装器
    • 1. 使用方法
    • 2. 包装器的应用场景:题目 - - 逆波兰表达式求值
    • 3. 成员函数 和 static 静态成员函数 使用 包装器
  • std::bind 适配器绑定
    • 1. 使用方法
    • 2. 调整参数 顺序
    • 3. 指定参数 / 参数个数的调整


std::function 包装器

std::function 包装器,也叫做 适配器。
头文件如下:

#include<functional>

类模板原型如下:

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

作用是 对 可调用对象类型(callable object) 进行 再封装适配
C++ 中的 function 本质上是一个类模板

首先我们如果针对相同的功能实现,可以选用下面的方法,但他们都有不同的类型:

  • 函数指针:类型比较难写
  • 仿函数(类中定义 operator()())
  • lambda:从底层角度来说其实也是仿函数

举例下面两个代码,都实现了数据的相加,使用方法是一样的,但是他们的类型完全不同。

#include<map>
#include<functional>	// 包装器的头文件// 函数写法
int func(int a, int b)
{cout << "int f(int a, int b)" << endl;return a + b;
}// 仿函数写法
struct Functor
{
public:int operator() (int a, int b){cout << "int operator() (int a, int b)" << endl;return a + b;}
};

即使他们的 返回值、参数列表 都相同。

如果有需求要这两个 函数 和 仿函数 声明出一个统一的类型,即 使用 map 的数据结构,把他们放入其中管理,他们类型完全不同,怎么写呢?

// map<string, xxx>			// 需要声明一个可调用的类型,xxx 这个类型怎么写呢
// int(*pf1)(int,int) = func;	// 这样写就没法和别的统一一个类型,无法和 map 规定的模板类型匹配

用 包装器 就可以适配出可调用类型。


1. 使用方法

包装器定义格式:function<返回值(参数列表)> func = 可调用对象

  • 可适配对象包括:
    函数指针 / 函数名、仿函数、lambda

  • function<返回值(参数列表)> 这个类型,就可以标识一系列 相同返回值、参数列表 的可适配对象了。

🌰具体用法如下图举例:

  1. 直接定义使用:
int main()
{function<int(int, int)> f1 = func;function<int(int, int)> f2 = Functor();		// 经过包装后这俩对象的类型就是一样的了function<int(int, int)> f3 = [](int a, int b) {cout << "[](int a, int b) {return a + b;}" << endl;return a + b;};cout << f1(1, 2) << endl;		// int f(int a, int b)cout << f2(10, 20) << endl;		// int operator() (int a, int b)cout << f3(100, 200) << endl;	// [](int a, int b) {return a + b;}return 0;
}
  1. 做模板参数使用
int main()
{map<string, function<int(int, int)>> opFuncMap;opFuncMap["函数指针"] = func;opFuncMap["仿函数"] = Functor();opFuncMap["lambda"] = [](int a, int b) {cout << "[](int a, int b) {return a + b;}" << endl;return a + b;};cout << opFuncMap["lambda"](1, 2) << endl;	// [](int a, int b) {return a + b;}return 0;
}

2. 包装器的应用场景:题目 - - 逆波兰表达式求值

👉🔗leetcode【150.逆波兰表达式求值】

给你一个字符串数组 tokens,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式,返回一个表示表达式值的整数。

注意:

  • 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是向零截断,表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例:

  • 输入:tokens = [“4”,“13”,“5”,“/”,“+”]
    输出:6
    解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
#include<stack>
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))	// 是操作符,将 st 中最 top 的两个数据 pop 出来,用 opfuncMap[str] 函数进行计算,把结果 push 进 st{int right = st.top();st.pop();int left = st.top();st.pop();st.push(opfuncMap[str](left, right));}else    // 不是操作符,即是操作数,push 到 st 中,等待操作符出现后进行计算{st.push(stoi(str));     // string 转 int}}return st.top();}
};

3. 成员函数 和 static 静态成员函数 使用 包装器

以这个类举例,里面有一个静态成员函数 plusi,一个非静态成员函数plusd:

class Plus
{
public:Plus(int rate = 2):_rate(rate) {}static int plusi(int a, int b) {return a + b;}double plusd(double a, double b) {return (a + b) * _rate;}private:int _rate = 2;
};

静态成员函数

  • 函数名就可以代表函数指针,不需要取地址。

非静态成员函数:

  • 需要写成 &函数名
  • 因为非静态成员函数还有一个隐藏的参数 this*,所以定义时还需要多声明一个参数,即非静态成员函数所属类的类型名
  • 调用的时候也需要多传一个 所属类的 对象\匿名对象
int main()
{// 静态成员函数,函数名就行function<int(int, int)> f1 = Plus::plusi;// 非静态成员函数,&函数名,另 多一个参数function<double(Plus, double, double)> f2 = &Plus::plusd;	// 这里其实是,Plus是类型,是通过我们创建的对象调用的 plusd	// 调用cout << f1(1, 2) << endl;cout << f2(Plus(), 20, 20) << endl;		// 非静态,多传一个匿名对象cout << f2(Plus(3), 20, 20) << endl;	// 还可以给匿名对象一个构造值Plus pl(3);								// 给有名对象当然也okcout << f2(pl, 20, 20) << endl;return 0;
}// 包装器的本质也是仿函数

至于,包装器定义非静态函数成员,传模板参数时,多传一个类类型作为参数,为了填补 this*,为啥不直接写成指针的格式?

因为,如果这里声明成指针,我们使用的时候就不能用匿名对象(因为匿名对象是右值不能取地址)这样调用了,所以把这类型包装器第一个参数设计成指针的写法不方便。

// 不推荐这样写 ~~~
function<double(Plus*, double, double)> f3 = &Plus::plusd;
Plus plus();	
cout << f3(&plus, 5, 3) << endl;

std::bind 适配器绑定

std::bind 函数定义在头文件中,是一个函数模板,原型如下:

template <class Fn, class... Args> 
/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2)  
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args); 

它就像一个函数包装器,接受一个可调用对象,生成一个新的可调用对
象来 适应和调整 原对象的 参数列表(个数、顺序)

  • 一般而言,我们用它可以把一个原本接收 N 个参数的函数 fn,通过绑定一些参数,返回一个接收 M 个参数的新函数(M 可以大于 N,但这么做没什么意义)。
  • 同时,使用 std::bind 函数还可以实现参数顺序调整等操作。

1. 使用方法

调用 bind 的一般形式:

auto newCallable = bind(callable, arg_list);
  • newCallable :本身是一个可调用对象
  • arg_list : 是一个逗号分隔的参数列表,对应给定的 callable 的参数。

当我们调用 newCallable 时,newCallable 会调用 callable,并传给它 arg_list 中的参数。

占位符:

arg_list 中的参数可能包含形如 xxx_n 的名字,其中 n 是一个整数,这些参数就是 占位符,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的 “位置”。

数值 n 表示生成的可调用对象中参数的位置:_1 为 newCallable 的第一个参数,_2 为第二个参数,以此类推。


综上,我们能知道 bind 实现的是对绑定函数参数的调整。

  • 当 bind() 中 arg_list 使用占位符的时候,占位符 可以调整原函数 参数顺序;
  • 当 arg_list 中传入具体参数的时候,就相当于原函数该参数位置被 指定,也就相当于在调用调整后的函数时,可以少传参数了,也就是对参数个数的调整。

2. 调整参数 顺序

使用占位符用来进行顺序的调整

🌰举例使用如下:

void print(int a, int b)
{cout << a << endl;cout << b << endl;
}
int main()
{print(10, 20);// _1 表示第一个参数,_2 是第二个function<void(int,int)> Rprint = bind(print, placeholders::_2, placeholders::_1);	//auto Rprint = bind(print, placeholders::_2, placeholders::_1);	Rprint(10, 20);	// 20 10return 0;
}

还是之前 给逆波兰表达式求值的代码,在用法上面扩展一下(只为使用语法):

int plus(int a, int b)
{return a + b;
}class Sub{
public:Sub(int rate = 3):_rate(rate) {}int func(int a, int b) {return (a - b)*_rate;}	// 这里 func 加 this* 有三个参数int operator()(int x, int y) {return x * y;}
private:int _rate;
};int mul(int x, int y)
{return x * y;
}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; }},	// lambda 表达式{"-",Sub()},								// 可以接收仿函数{"*",mul},									// 可以接受函数指针{"/",[](int x, int y) {return x / y; }},{"&",bind(&Sub::func,Sub(3),placeholders::_1,placeholders::_2)}};	// 别的参数都是两个,最后一个恰好是三个并且有一个能绑死,就刚好用在这里!// 代码略// 遍历给出的 逆波兰表达式 字符串// 看是否是算数操作符// 是操作符,将 st 中最 top 的两个数据 pop 出来,用 opfuncMap[str] 函数进行计算,把结果 push 进 st// 不是操作符,即是操作数,push 到 st 中,等待操作符出现后进行计算// return st.top();}
};

3. 指定参数 / 参数个数的调整

auto newCallable = bind(callable, arg_list);

arg_list 部分传入具体的参数,就代表该位置被 指定 了,调用调整后的函数就可以少传部分参数了。

🌰举例使用如下:

/*
class Sub{
public:Sub(int rate = 3):_rate(rate) {}int func(int a, int b) {return (a - b)*_rate;}	// 这里 func 加 this* 有三个参数int operator()(int x, int y) {return x * y;}
private:int _rate;
};
*/int main()
{// 不调整参数时function<int(Sub, int, int)> fsub = &Sub::func;	// 要加上取地址符cout << fsub(Sub(1), 10, 20) << endl;// bind 第一个参数function<int(int, int)> fsub2 = bind(&Sub::func, Sub(3), placeholders::_1, placeholders::_2);	cout << fsub2(10, 20) << endl;// bind 第二个参数:注意function<int(Sub, int)> fsub3 = bind(&Sub::func, placeholders::_1, 100, placeholders::_2);	cout << fsub3(Sub(2), 20) << endl;return 0;
}

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


相关文章:

【C++11】std::function 包装器(又叫适配器),std::bind 绑定

文章目录 std::function 包装器1. 使用方法2. 包装器的应用场景&#xff1a;题目 - - 逆波兰表达式求值3. 成员函数 和 static 静态成员函数 使用 包装器 std::bind 适配器绑定1. 使用方法2. 调整参数 顺序3. 指定参数 / 参数个数的调整 std::function 包装器 std::function 包…...

Linux系统编程系列之线程

一、什么是线程 线程&#xff08;Thread&#xff09;是计算机中的基本执行单元&#xff0c;是操作系统调度的最小单位。线程是进程内的一个独立执行流程&#xff0c;一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff0c;但每个线程都有自己的独立栈空间以及…...

CV面试知识点总结

一.卷积操作和图像处理中的中值滤波操作有什么区别&#xff1f; 1.1卷积操作 卷积操作是一种线性操作&#xff0c;通常用于特征的提取&#xff0c;通过卷积核的加权求和来得到新的像素值。1.2中值滤波 原文&#xff1a; https://blog.csdn.net/weixin_51571728/article/detai…...

Centos一键安装、切换各版本JDK

查看服务中的安装的jdk rpm -qa | grep java获取jdk各版本信息 yum -y list java*查看指定版本 yum -y list java*|grep 1.8安装jdk yum install java-11-openjdk当服务器中有多个版本jdk&#xff0c;切换指定jdk版本 alternatives --config java按照提示输入编号即可切换&…...

JavaWeb项目:smbms(mysql)

1.准备工作&#xff0c;创建数据库 CREATE DATABASE smbms;USE smbms;CREATE TABLE smbms_address (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID,contact VARCHAR(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 联系人姓名,addressDesc VARCHAR(50) COLLATE u…...

shell脚本的多线程介绍

shell脚本的多线程介绍 shell脚本中&#xff0c;实现多线程可以使用以下方法&#xff1a; 1&#xff09;使用&符号 在Shell中&#xff0c;可以使用&符号将命令放在后台执行&#xff0c;这样就可以同时执行多个命令。例如&#xff1a; #!/bin/bash command1 & #…...

周记之反思

9.25 这篇总结我承认&#xff0c;是在26号上午写的&#xff0c;那昨天晚上又聊天了&#xff0c;但是对比之前来说好很多了&#xff0c;所以26号上午也就是今天我起了个大早&#xff0c;然后把昨天的尾巴收了一下&#xff0c;没收完&#xff0c;先说说成果&#xff1a; 完成了…...

信创办公–基于WPS的EXCEL最佳实践系列 (数据整理复制粘贴)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;数据整理复制粘贴&#xff09; 目录 应用背景操作步骤1、数据查找与替换2、复制或粘贴数据3、使用自动填充工具4、将数据拆分到多列5、应用数字格式 应用背景 数据的整理复制粘贴等在日常的工作中经常使用。本章内容主要学习…...

二极管的直流等效电路和微变等效电路

二级管的主要参数 1.IF&#xff08;最大整流的电流&#xff09; 二极管长期工作做能够通过电流的平均最大值&#xff1a;物理意义&#xff1a;功率电流值。 2.UR 二极管最高反向工作电压 需要留有裕度&#xff0c;通常能达到一半的裕度&#xff1b;UR不能等于UBR。 3.IR 未击穿…...

Python无废话-基础知识字典Dictionary详讲

“字典Dictionary” 是一种无序、可变且可嵌套的数据类型&#xff0c;用于存储键值对。字典使用花括号{}来定义&#xff0c;并用逗号分隔键值对。本文对字典常使用方法&#xff0c;创建字典、添加字典、删除字典、如何获取字典做了知识归纳。 字典有以下几个特征&#xff1a; …...

ChatGPT多模态升级,支持图片和语音,体验如何?

一、前言 9 月 25 日&#xff0c;ChatGPT 多模态增加了新的语音功能和图像功能。这些功能提供了一种新的、更直观的界面&#xff0c;允许我们与 ChatGPT 进行语音对话或展示我们正在谈论的内容。 ChatGPT 现在可以看、听、和说话了&#xff0c;而不单单是一个文本驱动的工具了。…...

(SAR)Sentinel-1影像自动下载

基于ASF网站提供的python代码&#xff0c;实现Sentinel-1影像的自动下载&#xff1b; 1、登录ASF网站 登录Sentinel-1影像ASF网站&#xff1a;https://search.asf.alaska.edu/&#xff1b; 点击网站最右侧Sign in图标&#xff0c;进行用户注册&#xff1b; 注册完用户之后&…...

设计模式10、外观模式Facade

解释说明&#xff1a;外观模式&#xff08;Facade Pattern&#xff09;又称为门面模式&#xff0c;属于结构型模式 Faade 为子系统中的一组接口提供了一个统一的高层接口&#xff0c;该接口使得子系统更加容易使用 外观&#xff08;Facade)角色&#xff1a;为多个子系统对外提供…...

华为数通方向HCIP-DataCom H12-831题库(单选题:181-200)

第181题 以下关于OSPF的5类LSA中的转发地址(ForwardingAddress,FA) 的描述,正确的是哪一项? A、当FA地址为0.0.0.0时,收到该LSA的路由器认为到达目的网段的数据包应该发往对应的ABR,因此将到达ABR的下一跳地址作为这条外部路由的下一跳 B、当FA地址为0.0.0.0时,收到该LS…...

Java 中的参数传递方式

Java 中的参数传递方式通常被称为“值传递”&#xff0c;这意味着在方法调用时&#xff0c;实际上传递给方法的是变量的副本&#xff0c;而不是变量本身。尽管这被广泛称为“值传递”&#xff0c;但需要注意的是&#xff0c;这并不意味着 Java 不支持引用传递。事实上&#xff…...

从0开始python学习-27.selenium 简单登录页面脚本

url https://test.com.cn/login driver.get(url)# 获取登录页面需要输入账号密码进行模拟登录操作 user driver.find_element(By.XPATH,//*[id"username"]).send_keys(username) pwd driver.find_element(By.XPATH,//*[id"selfpwd"]).send_keys(123456)…...

华为智能企业上网行为管理安全解决方案(2)

本文承接&#xff1a; https://blog.csdn.net/qq_37633855/article/details/133339254?spm1001.2014.3001.5501 重点讲解华为智能企业上网行为管理安全解决方案的部署流程。 华为智能企业上网行为管理安全解决方案&#xff08;2&#xff09; 课程地址方案部署整体流程组网规划…...

【python海洋专题九】Cartopy画地形等深线图

【python海洋专题九】Cartopy画地形等深线图 水深图基础差不多了&#xff0c;可以换成温度、盐度等 本期加上等深线 本期内容 1&#xff1a;地形等深线 cf ax.contour(lon, lat, ele[:, :], levelsnp.linspace(-9000,-100,10),colorsgray, linestyles-,linewidths0.25, t…...

Java后端模拟面试,题集①

1.Spring bean的生命周期 实例化 Instantiation属性赋值 Populate初始化 Initialization销毁 Destruction 2.Spring AOP的创建在bean的哪个时期进行的 &#xff08;图片转载自Spring Bean的完整生命周期&#xff08;带流程图&#xff0c;好记&#xff09;&#xff09; 3.MQ如…...

UE5.1编辑器拓展【二、脚本化资产行为,快速更改资产名字,1.直接添加前缀或后缀2.通过资产类判断添加修改前缀】

目录 了解相关的函数 第一种做法&#xff1a;自定义添加选择资产的前缀或后缀 代码 效果 第二种做法&#xff1a;通过映射来获取资产类型添加前缀和修改前缀 映射代码 代码 效果 在之前一章中&#xff0c;我们创建了插件&#xff0c;用来扩展编辑器的使用&#xff1a; …...

深度学习模型压缩:从原理到实践

深度学习模型压缩&#xff1a;从原理到实践 1. 背景与动机 深度学习模型在各种任务上取得了显著的性能提升&#xff0c;但随之而来的是模型规模的不断增长。大型模型虽然性能优异&#xff0c;但也带来了以下问题&#xff1a; 存储需求大&#xff1a;大型模型需要大量存储空间&a…...

STM32串口环形队列IAP固件更新方案

基于STM32串口环形队列的IAP实现方案1. 项目概述1.1 系统架构本方案实现了一种基于STM32F103C8T6微控制器的串口IAP(In-Application Programming)系统&#xff0c;采用环形队列缓冲机制解决有限SRAM空间下的固件更新问题。系统将64KB Flash空间划分为四个功能区域&#xff1a;B…...

全网最详细的AI产品经理学习路线,非常详细收藏这一篇就够了

前言 AI产品经理作为一个新兴且热门的职业&#xff0c;不仅需要具备传统产品经理的能力&#xff0c;还需要对AI技术有深入的理解和应用。本学习路线旨在帮助有志于成为AI产品经理的学习者系统地掌握所需的知识和技能。 前排提示&#xff0c;文末有大模型AGI-CSDN独家资料包哦…...

ESP WiFi中继器终极配置指南:从零开始打造智能无线网络扩展器

ESP WiFi中继器终极配置指南&#xff1a;从零开始打造智能无线网络扩展器 【免费下载链接】esp_wifi_repeater A full functional WiFi Repeater (correctly: a WiFi NAT Router) 项目地址: https://gitcode.com/gh_mirrors/es/esp_wifi_repeater 想要轻松扩展WiFi覆盖范…...

轻量级工具G-Helper:一站式解决ROG游戏本色彩配置异常问题

轻量级工具G-Helper&#xff1a;一站式解决ROG游戏本色彩配置异常问题 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops. Control tool for ROG Zephyrus G14, G15, G16, M16, Flow X13, Flow X16, TUF, Strix, Scar and other models 项目…...

统信系统下如何管理Mysql?

背景 看到标题很多朋友会打趣的问我&#xff1a;“你不是一直用麒麟操作系统做讲解吗&#xff1f;”&#xff0c;其实DBCS和DESK的兼容性太强了&#xff0c;什么操作系统都行&#xff0c;Windows上最容易了&#xff0c;所以我一般不用Windows&#xff0c;下次我用Ubuntu给大家…...

STM32F103C8T6 HAL库驱动HC-SR04:用输入捕获双通道模式,精准测距不翻车

STM32F103C8T6 HAL库双通道捕获HC-SR04&#xff1a;高精度测距的工程实践 在智能小车避障、工业液位检测等嵌入式应用中&#xff0c;超声波测距模块的稳定性直接决定系统可靠性。传统单通道捕获方案常因计数器溢出、中断响应延迟等问题导致测量误差&#xff0c;而双通道输入捕获…...

LIBPNG深度解析:构建企业级PNG处理架构的技术决策指南

LIBPNG深度解析&#xff1a;构建企业级PNG处理架构的技术决策指南 【免费下载链接】libpng LIBPNG: Portable Network Graphics support, official libpng repository 项目地址: https://gitcode.com/gh_mirrors/li/libpng LIBPNG作为PNG格式的官方参考实现库&#xff0…...

Qwen3.5-35B-A3B-AWQ-4bit惊艳效果:电路图元件识别+故障原因中文推理

Qwen3.5-35B-A3B-AWQ-4bit惊艳效果&#xff1a;电路图元件识别故障原因中文推理 1. 模型能力展示 Qwen3.5-35B-A3B-AWQ-4bit作为一款面向视觉多模态理解的量化模型&#xff0c;在电路图分析和故障诊断领域展现出令人惊艳的能力。这个经过4bit量化的模型不仅保持了原版35B参数…...

沃虎电子:SFP连接器在高速光模块中的应用与选型要点

SFP&#xff08;Small Form-factor Pluggable&#xff09;连接器是现代光通信设备的核心接口组件&#xff0c;广泛应用于交换机、服务器、光模块等设备。随着数据中心向400G/800G演进&#xff0c;SFP连接器的性能要求不断提升。本文从工程实践角度&#xff0c;系统介绍SFP连接器…...