【C++】C++11的新特性 — function 包装器 , bind包装器

news2024/9/20 22:58:28

在这里插入图片描述

有些人的生活,可以轻轻松松,有些人的生活就是奥运会,生下来就在跑道上,如果不去全力奔跑,注定会被淘汰,更何况,即使努力奔跑,也未必能战胜很多人。

-- 傅首尔 --

C++11的新特性

  • 1 function包装器
    • 1.1 function的底层
    • 1.2 开始使用function
    • 1.3 包装成员函数指针
  • 2 bind包装器
    • 2.1 bind的底层
    • 2.2 开始使用bind
    • 2.3 bind绑定的实际应用
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 function包装器

1.1 function的底层

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

在C++中有一个可调用对象的概念,其中有几个奇葩:函数指针,仿函数对象,lambda表达式。祖师爷看这几个玩意儿很难受:

  1. 函数指针 — 类型定义复杂
  2. 仿函数对象 — 要定义一个类,用的时候很,麻烦,不适合统一类型
  3. lambda表达式 — 没有类型概念

所以包装器就来包装上面的复杂东西,可以做到统一类型,就像秦王统一度量衡一样!我们想来看包装器的底层是什么样子的:

// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

模板参数说明:

  1. Ret: 被调用函数的返回类型
  2. Args…:参数包 ,被调用函数的形参

支持非常多的构造:
在这里插入图片描述
我们继续看最底层是什么:
在这里插入图片描述
看到里面重载了operator(),所以其实包装器的底层是仿函数!

1.2 开始使用function

包装器不是用来定义可调用对象的,是用来包装可调用对象的。也就是可以包装所有的可调用对象,尤其是这仨货:函数指针,仿函数对象,lambda表达式。我们先来练练手:
假如有这样一个仿函数,要如何来进行包装呢?

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

包装器的包装方式很不一样,我们上面看到过包装器的底层,其中的模版参数是
在这里插入图片描述
返回值类型(参数类型)这样的,很好理解!


struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

int main()
{
	//看起来很好理解!
	function<int(int, int)> func = Functor();
	
	return 0;
}

同样的也可以包装函数指针,lambda表达式!:

#include <functional>

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
int add(int a, int b)
{
	return a + b;
}

int main()
{
	function<int(int, int)> func = Functor();
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = add;
	cout << func1(1, 2) << endl;
	// 仿函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lambda表达式
	std::function<int(int, int)> func3 = [](const int a, const int b) { return a + b; };
	cout << func3(1, 2) << endl;

	return 0;
}

非常好用,调用起来非常丝滑:
在这里插入图片描述
假设包装的时候类型不匹配怎么办?肯定就会报错了!包装器内部将可调用对象进行储存起来,封装了一层来进行调用。但是为什么不直接来进行调用,而是进行包装呢?进行一个统一,让代码更加优雅,让代码更加好用,我们来看一个经典的题:
在这里插入图片描述
对于这个题目,之前我们解法是使用一个栈,依次存入数字,取到运算符时就进行运算。对于这个运算符的判断,可以通过多重if语句进行判断或者Switch语句。我们学习了包装器就可以进行进行一个优雅的方式了:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //一般解法是调用一个栈来做到取用数字
        //可以通过包装器来实现代码优雅化
    map<string , function<int(int, int)> > math 
    {
        { "+" , [](int a, int b) {return b + a; } },
        { "-" , [](int a, int b) {return b - a; } }, 
        { "*" , [](int a, int b) {return b * a; } },
        { "/" , [](int a, int b) {return b / a; } }
    };
    
        
        //调用一个stack
        stack<int> tmp;
        //从头开始遍历
        for(int i = 0 ; i < tokens.size() ; i++)
        {
            string t = tokens[i];

            if( t.size() == 1 && !('0' <= t[0] && t[0] <= '9'))
            {
                int a = tmp.top(); tmp.pop();
                int b = tmp.top(); tmp.pop();

                if(t[0] == '+') tmp.push(math[t](a , b));
                if(t[0] == '-') tmp.push(math[t](a , b));
                if(t[0] == '*') tmp.push(math[t](a , b));
                if(t[0] == '/') tmp.push(math[t](a , b));

            }
            //是数字就进行插入
            else
            {
                tmp.push(stoi(t));
            }
           
        }
        return tmp.top();
    }
};

这样就优雅的进行了解决!

1.3 包装成员函数指针

我们来看一个特别的:对于对象里面的函数如何进行包装呢?
对象里的函数可以分为两种:静态成员函数,普通成员函数
对于静态函数指针直接进行包装就可以,普通函数指针需要添加&,并且要注意普通成员函数有默认参数``。所以为了统一就都加上&

class Plus
{
public:
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return a + b;
    }
};

int main()
{
	//需要指明类域
	//对于静态的成员函数直接进行调用就可以
    //function<int(int, int)> func1 = Plus::plusi;
    function<int(int, int)> func1 = &Plus::plusi;
    //普通成员函数不可以这样进行 , 和上面一样会出现报错
    //需要&类::函数名 , 隐藏的参数
    function<double(Plus* , double, double)> func2 = &Plus::plusd;

    return 0;
}

使用的时候,,静态成员函数可以直接拿来使用,但是对于类的普通函数需要实例化一个类,一并传入才可以:

func1(1 , 2);
//实例化一个类
Plus plus;
func2(&plus , 1.1 , 2.2);

当然肯定有简单的方法,我们可以在包装的时候,做一下处理:将第一个参数改成类,而不是类指针!这样让人很不理解,怎么看怎么不对!确实可以用

function<double(Plus , double, double)> func2 = &Plus::plusd; 
//...
//可以直接传入匿名对象了
func2(Plus() , 1.1 , 2.2);

其中的原因我们来分析一下:function<double(Plus , double, double)> func2 = &Plus::plusd; 中的模版参数会不会直接传入到类对象中呢?不会的!因为this不会进行直接的显示调用,我们可以猜测包装器内部应该是通过这个对象来进行调用!

2 bind包装器

2.1 bind的底层

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

其实和function的工作很像,多增加了一下模版参数,支持了参数的包装!可以称作绑定!

我们先来看原型:

// 原型如下:
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);

其返回值是/* unspecified */ 不明确的,认为没有返回值!很神奇奥!
对于这个底层,我们稍微了解就可以!

2.2 开始使用bind

bind 的用途是用来调整可调用对象的参数个数或者顺序,就是我们可以把一个可调用对象包装起来,我们可以在包装器这层调整其参数的顺序!里面有一个placehodlers命名空间
在这里插入图片描述
_n代表第几个参数

来看一个最直观的:

int sub(int a , int b)
{
	return a - b;
}

int main()
{
	//正常顺序调用
	auto func1 = sub;
	cout << func1(10, 5) << endl;
	
	auto func2 = bind(sub, placeholders::_1, placeholders::_2);
	cout << func2(10, 5) << endl;
	//调整参数顺序调用
	auto func3 = bind(sub, placeholders::_2, placeholders::_1);
	cout << func3(10, 5) << endl;

	return 0;
}

我们运行:
在这里插入图片描述
就可以理解这个调整参数顺序是什么意思了。_n代表的是新产生的包装器的参数的顺序!
在这里插入图片描述

这样我们就可以继续来了解:假如我们要对类函数进行包装:

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{
	//这里其实是是有三个参数,bind(&Sub::sub, placeholders::_1, placeholders::_2 , placeholders::_3)
	//这里和function一样有两种写法 传指针和穿对象都可以
	auto func4 = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
	//这样就省略了一个参数
	cout << func4(10, 5) << endl;
	return 0;
}

这样就调整了参数个数。

在这里插入图片描述
通过对参数的个数和顺序的调整就可以实现了对可调用对象参数的调整!

2.3 bind绑定的实际应用

我们设想一个游戏场景,每个英雄都有一定血量和蓝量。我们设计一个英雄类来记录这些基本信息。
现在遇到了群体debuff,每个英雄都会受到影响,但是效果不同。我们可以通过:

  1. 在类对象中加入特定函数来实现对特定对象的修改,但是这样会是我们的代码很不优雅!
  2. 所以可以设计一个特定函数,通过一个bind绑定到对应对象中,方便调用!

我们在类外实现一个debuff函数,然后通过bind绑定到对象上,为保证可以修改到,一定注意是使用引用!!!


#include<vector>
#include<string>
#include<functional>

class Hero
{
public:
	Hero(string name , int blood , int blue):
		_name(name) ,
		_blood(blood),
		_blue(blue)
	{	
	}


public:
	int _blood;
	int _blue;
private:
	string _name;
};

void hurt(Hero& hr , int blood_n , int blue_n)
{
	hr._blood -= blood_n;
	hr._blue -= blue_n;
}


int main()
{
	Hero WK("孙悟空", 100, 100);
	Hero SZ("唐三藏", 80, 150);
	Hero BJ("八戒", 120, 80);
	Hero SS("沙僧", 80, 120);

	//假如有一个场景需要对英雄造成伤害,但伤害不同
	//可以写一个可调用对象来统一绑定
	//ref(WK)注意一定要加入ref,保证是进行的引用!
	auto func_WK = bind(hurt , ref(WK), placeholders::_1 , placeholders::_2);
	auto func_SZ = bind(hurt , ref(SZ), placeholders::_1 , placeholders::_2);

	func_WK( 20, 10);
	func_SZ( 10, 20);

	return 0;
}

这样就可以调用专门的函数来对每个对象进行处理了!
非常优雅奥!!!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

判断字符串是否接近:深入解析及优化【字符串、哈希表、优化过程】

本文将详细解析解决这个问题的思路&#xff0c;并逐步优化实现方案。 问题描述 给定两个字符串 word1 和 word2&#xff0c;如果通过以下操作可以将 word1 转换为 word2&#xff0c;则认为它们是接近的&#xff1a; 交换任意两个现有字符。将一个现有字符的每次出现转换为另…

SQL进阶技巧:多行转列问题中如何保证不同字段内容有序性及一一对应?【collect_list函数有序性保证问题】

目录 0 问题描述【小红书面试题】 1 数据准备 2 问题分析 3 小结 0 问题描述【小红书】 有如下需求,需要将左边的表变换成右边的表,注意字段内容的顺序及对应内容的一致性。 第一个字段为name,第二个字段为subject,第三个字段为score,变换后要求subject按照语文、数学…

android13 禁止某个app接口某个广播 禁止应用接受开机广播 禁止应用接收广播

总纲 android13 rom 开发总纲说明 目录 1.前言 2.问题分析 3.代码更改 4.彩蛋 1.前言 我们在定制系统的过程中,有时候,有些客户的应用的一些表现,并不能满足需求。例如应用接收了开机广播,然后做了一些事情,起调了某些activity。或者接受了某个广播,做了一些操作等…

网络安全第一次作业(ubuntuan安装nginx以及php部署 and sql注入(less01-08)))

ubuntuan安装nginx以及php部署 1.安装依赖包 rootadmin123-virtual-machine:~# apt-get install gcc libpcre3 libpcre3-dev zliblg zliblg-dev openssl libssl-dev2.安装nginx 到https://nginx.org/en/download.html下载nginx 之后将压缩包通过xtfp传输到ubuntu的/usr/loc…

Android:Uniapp平台中接入即构RTC+相芯美颜

0 前言 前阵子使用Uniapp平台开发了一个跨平台app&#xff0c;并且接入了即构RTC后&#xff0c;今天想进一步丰富app的直播功能。之前有相芯美颜的开发经验&#xff0c;打算将相芯美颜接入即构RTC. **在DCloud插件市场找到了在即构RTC接入相芯美颜插件&#xff0c;https://ex…

Golang | Leetcode Golang题解之第324题摆动排序II

题目&#xff1a; 题解&#xff1a; func wiggleSort(nums []int) {n : len(nums)x : (n 1) / 2target : quickSelect(nums, x-1)transAddress : func(i int) int { return (2*n - 2*i - 1) % (n | 1) }for k, i, j : 0, 0, n-1; k < j; k {tk : transAddress(k)if nums[t…

STM32之GPIO(General Purpose Input/Output,通用型输入输出)

文章目录 前言一、GPIO简介二、GPIO结构2.1 GPIO基本结构2.2 GPIO位结构2.2.1 输入部分2.2.1 输出部分 四、GPIO模式4.1 浮空/上拉/下拉输入4.2 模拟输入4.3 开漏/推挽输出4.4 复用开漏/推挽输出 前言 提示&#xff1a;本文主要用作在学习江协科大STM32入门教程后做的归纳总结…

【数据结构-前缀哈希】力扣523. 连续的子数组和

给你一个整数数组 nums 和一个整数 k &#xff0c;如果 nums 有一个 好的子数组 返回 true &#xff0c;否则返回 false&#xff1a; 一个 好的子数组 是&#xff1a; 长度 至少为 2 &#xff0c;且 子数组元素总和为 k 的倍数。 注意&#xff1a; 子数组 是数组中 连续 的部…

SpringBoot快速学习

目录 SpringBoot配置文件 多环境配置 SpringBoot整合junit SpringBoot整合mybatis 1.在创建时勾选需要的模块 2.定义实体类 3.定义dao接口 4.编写数据库配置 5.使用Druid数据源 SpringBoot 是对 Spring 开发进行简化的。 那我们先来看看SpringMVC开发中的一些必须流程…

C++ | Leetcode C++题解之第324题摆动排序II

题目&#xff1a; 题解&#xff1a; class Solution { public:int partitionAroundPivot(int left, int right, int pivot, vector<int> &nums) {int pivotValue nums[pivot];int newPivot left;swap(nums[pivot], nums[right]);for (int i left; i < right; …

【Buffer Pool】定长内存池的实现

创建一个大块的内存内存 1.内存的类型是什么&#xff1f; char* 方便有多少字节就乘以多少字节 2.如何还回来内存&#xff1f;可以将换回来的小块的内存块链接起来&#xff0c;使用freeList 3.如何链接起来? 让上一个内存块的数据存下一个内存块的地址即可 4.如果内存块的…

Mybatis-plus乐观锁

为什么要用锁 原因是当两个线程并发修改同一条数据时候 例如有条数据 id 1 count(金额/数量) 500 有两个线程都在查询数据库 查出来都是 1 500 现在两个线程都要修改这条数据 在原来基础上20 和30 那么理论来讲应该是550 可是实际有可能是530 原…

点双联通分量和边双联通分量如何选择?

先讲一下 &#xff0c;双联通分量 一定是用于 无向图 考虑什么时候需要用边双联通分量呢&#xff1f;&#xff0c;考虑给你的是一个一般图&#xff0c;需要你把联通的点都缩起来&#xff0c;视作一个点的情况&#xff0c;就是说割点可以反复访问&#xff0c;就是说割点和其他点…

鸿蒙应用服务开发【华为支付服务】 服务端

介绍 华为支付云侧接口 Java SDK Sample。 官方 Java 语言开发库pay-java由 core 和 service 组成&#xff1a; core 为基础库。包含自动签名和验签的 HTTP 客户端、回调处理、加解密库。service 为业务服务。基于业务场景提供不同的业务类&#xff0c;其下的方法为对应的ht…

openai-dotnet:OpenAI官方提供的.NET SDK库!

自从ChatGPT大火以来&#xff0c;针对OpenaAI提供的API接口&#xff0c;封装的SDK库非常多。 之前也推荐过几个.Net版本&#xff0c;今天推荐下OpenAI官方提供的.NET 库&#xff01; 01 项目简介 openai-dotnet是OpenAI 官方提供的 .NET库&#xff0c;用于方便.NET应用程序中…

【Java数据结构】---初始数据结构

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 前言 从今天开始我们就要学习Java…

搭建基于树莓派的Linux学习环境(TODO)

主要是想学一下Linux内核&#xff0c;所以搭一套环境&#xff0c;其实有几个选择&#xff0c;都是我买了板子的。 首先是正点原子的RK3568&#xff0c;最早是想弄安卓&#xff0c;但是SDK的大小真的把我劝退了&#xff0c;动不动几百个G的空间&#xff0c;还有就是保底16个G的…

108 将有序数组转换为二叉搜索树

解题思路&#xff1a; 平衡二叉树&#xff0c;又称自平衡二叉搜索树&#xff08;简称AVL树&#xff09;&#xff0c;其特点如下: 每个子树都为平衡二叉树高度平衡&#xff1a;任意节左子树与右子树高度差不超过1排序树&#xff1a;左子树的所有节点的值小于该节点&#xff0c;…

算法回忆录(3)

11. 假设有7个物品&#xff0c;它们的重量和价值如下表所示。若这些物品均不能被分割&#xff0c;且背包容量M&#xff1d;150&#xff0c;设计算法求解怎么装才能使得获取的价值最大&#xff1f;请写出伪代码。 #include <stdio.h>#define MAX_ITEMS 100 #define …

怎么读取FRM、MYD、MYI数据文件

一、介绍frm、MYD、MYI文件 在MySQL中&#xff0c;使用MyISAM存储引擎时&#xff0c;数据库表会被分割成几个不同的文件文件描述功能扩展名FRM 文件表结构定义文件存储表的结构信息&#xff0c;字段、索引等.FRMMYD 文件数据文件包含表的实际数据.MYD&#xff08;MYData&#x…