C++ - 包装器 - bind()函数

news2024/11/17 20:39:57

包装器

在 C++ 当中可能会有各种各样的可调用类型,比如 函数指针,仿函数,lambda 等等,那么这么多的可调用类型,我们在使用的时候就会犯迷糊,那可不可以统一控制一下呢?

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

int main()
{
	// 函数名(函数名就相当于是函数指针)
	cout << useF(f, 11.11) << endl;
	// 函数对象(仿函数)
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

上述有三种调用方式,分别是:函数指针,仿函数 和 lambda表达式。

每一个 不同类型的 函数,在同一个 函数模版当中会实例化出 三份 函数,因为 f 这个变量,接收的是 可调用对象,三种不同的可调用对象的传入,可能是要实例化出 不同的函数出来的,所以,对于上述 static 的 count 静态变量输出的地址是不一样的

通过上面的程序验证,我们会发现useF函数模板实例化了三份

在主函数当中,三种方式都可以调用,也就是说:

ret = func(x);
上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能
是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率下!

那么,有没有方式和把上述的三种方式都统一下呢?

答案是有的。

就是使用包装器。

 包装器语法

 std::function  在头文件  <functional> 当中,其实 function 包装器本质上是一个 类,在这个类当中存储了 各种函数,有上述的 仿函数,lambda,函数指针都都可以存储其中。

function 类模板原型如下所示:

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

 模板参数说明

  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

 使用包装类的话,就是把 上述的可调用对象存储到一个容器当中(function对象当中),通过这个包装类,把 这些可调用对象包装一遍,这样我们在调用这个些个 可调用对象的时候,就可以以统一的方式进行调用了。

比如,在上述我们写了三个函数,在返回值上就是都是 double 类型,而且函数的参数个数只有一一个:

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

    return 0;
}

那么包装类声明应该这样写:

// 包装函数指针
function<double(double)> f1 = f;

// 包装lambda表达式
function<double(double)> f2 = [](double d)->double { return d / 4; };

// 包装仿函数(传入仿函数类的匿名对象)
function<double(double)> f3 = Functor();

那么,此时就包装好了 这三个函数。

在没有使用包装类的时候,我们要想把这个些 可调用对象 存入到一个 容器,比如像 vector 当中是不可能的。

但是现在这三个函数已经被 function 包装类,包装成一个对象了,那么此时,我们就可以往vector 当中存储 一个个 类型为 function<double(double)> 的一个个 包装类对象(如上述的 f1  f2  f3 )。

如下所示,我们拿  vector 容器把上述的三个对象存入其中:

vector<function<double(double)> v = {f1 , f2  , f3 };

包装类本质上解决是什么问题呢?

在以前,我们想把 仿函数的可调用对象存储到一个容器当中,那么,这个容器的类型就只能是这个仿函数的类型,那么就只能存储这个这种类型的仿函数,想函数指针就不能一起存储了。

而且,像lambda表达式 的 可调用对象的类型我们根本不好控制,他是 lambda + uuid 的方式来命名的类型,基本上每一个 lambda表达式的可调用对象的类型都是不一样的。那么对于 同返回值类型 和 同参数列表的 lambda表达式 都不能存储在一起。

而像上述 一样 ,通过 function 包装类 包装过后,这些 可调用对象 就都被包装成了一个 function<返回值参数类型(参数包)> 这样类型的 function 对象,那么在vector 当中只需要存储 每一个 可调用对象包装成的 function 对象,就可以把 不同 类型的 可调用对象 存储在一起了:

vector<function<double(double)> v = {f1 , f2  , f3 };

double n = 1.1;
for(auto f : v)
{
    cout << f(n++) << endl;
}

输出:

 而且,我们都不用 像 上述的方式来写,不同多定义出 f1 f2 这些 function 的对象,可以直接 把函数的可调用对象传入进去

vector<function<double(double)> v = {
                                f 
                                , [](double d)->double { return d / 4; }
                                ,Functor()
                                };

上述的 vector 只是举一个例子,意思就是可以把 一个函数 利用包装类,包装成一个 function 对象,把 函数的可调用对象存储到一个 容器当中。

在使用 包装类, 包装两三个函数之后,这个三个可调用对象都被包装成 同一个 类型的 对象了,那么我们接下来在传入这三个包装之后的对象,因为这个三个对象的类型是一样 ,所以使用的是一个实例化出来的函数,也就意味着这三个函数就用的是一个 useF 的实例化函数了

// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;

// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;

// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
cout << func3(1, 2) << endl;

输出: 

 如上述,我们发现 count 的地址都是一样的,而且在三个函数当中对 count 的修改都能延伸。

 证明了 三个 可调用对象 的类型是一样的。


比如下述的引用场景:

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

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

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

 如果不用 上述包装器的话,我们是使用 if - else 或者是 switch 的方式来手动判断 当前是哪一个 操作符,然后在调用这个 操作符对应的 算法:

 可以发现,非常的麻烦。

我们可以使用键值对的方式来把 ,上述的 操作符 和 这个操作符对应的算法给 匹配起来。但是,算法是一行一行的代码,我们要想把代码存储到 一个 容器当中,就需要 像上述一样,写成一个函数,然后把这个函数的可调用对象包装成 一个 function 的对象,map 才能存储这个对象。

那么 ,map 当中就可以实现了, 一个 运算符 对应 一个 函数的算法(可调用对象包装出的 function 对像):

 向上述一样,我们使用 看起来最简便的 lambda 表达式,不使用 仿函数 这种使用起来 相对 lambda 来说比较臃肿。但是是因为上述的代码都比较少,如果是 代码量比较多的话,仿函数的方式分开写也是对于 可读性是有提高的。

bind

std::bind 

 是一个函数模版,它就像一个函数适配器(包装器)一样,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

一般而言,我们可以用 bind 函数,传入一个 某一个函数 FN,这个FN函数当中有 N 个参数,通过绑定一些参数,返回一个接受 M 个 (M > N 是可以的,但是没有必要) 参数 的新函数(这个新函数是以 function 对象方式返回的),我们可以使用 bind 函数对这个新函数当中的 参数顺序进行调整

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

使用 bind 函数可以把上述 func函数 的 a 和 b 的参数位置进行交换。在函数当中相当于是 a 变成 b,b 变成a。

	function<int(int, int)> rSub2 = bind(func, placeholders::_2, placeholders::_1);
	cout << rSub2(10, 5) << endl;

输出:

-5

我们看到,如果传入 10 和 5 的话,在func()函数返回结果是 5,但是 经过 bind 修改后的 返回的 rSub 对象的返回结果是 -5。

而且,bind()函数只是对 func()原函数的基础之上,重新创建了一个新的函数,对这个新的函数进行修改,并不会影响到 func()原函数:
 

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

输出:
 

5

bind语法 

我们看到,上述使用了 placeholders ,这个其实是一个命名空间,这个命名空间声明了未指定数量的对象:_1,_2,_3,…,用于在调用bind函数时指定占位符:

 我们拿上述例子来说明语法:

 _1 就代表的是 func 函数的第一个参数, _2 代表 func 的第二个参数·······以此类推。

 它相当于是 placeholders::_1 只 接受在调用函数时候传入的第一个参数,如上述的10; placeholders::_2 只 接受在调用函数时候传入的第二个参数,如上述的5·······


如果,我们使用的函数参数比较多的话,或者是同事,别人写的函数采纳数定义顺序我们写着不习惯,我们就可以选择调换参数位置,来符合我们的调用习惯。


 bind()函数的缺省参数写法

如果我们不想传某个参数,可以用 bind 函数给定缺省值。

如下面这个例子:

void func(int a, int b, double rate)
{
    return (a - b) * rate;
}

int main()
{
    // function对象的类型当中的模版参数不要写缺省参数的类型
    function<double(int, int) > plus1 = bind(func, placeholders::_1, placeholders::_2, 4.0);
    function<double(int, int) > plus2 = bind(func, placeholders::_1, placeholders::_2, 4.2);
    function<double(int, int) > plus3 = bind(func, placeholders::_1, placeholders::_2, 4.4);

    // 调用的时候也不用写 缺省参数的类型
    cout << plus1(10, 5) << endl;
    cout << plus2(10, 5) << endl;
    cout << plus3(10, 5) << endl;

    return 0;
}

 输出:
 

20
21
22

 上述 的 4.0  4.2  4.4  位置就是我们在 bind函数 当中的设置的缺省参数值,设置的缺省参数对应 func 当中的 rate 这个参数。 

 当然,如果 要缺省的参数位置改变,对应的 palceholders::_? 也需要改变吗?

如下所示:

double func(double rate ,int a, int b)
{
    return (a - b) * rate;
}

function<double(int, int) > plus1 = bind(func, 4.0, placeholders::_2, placeholders::_3); // 编译报错
function<double(int, int) > plus2 = bind(func, 4.2, placeholders::_1, placeholders::_2); // 编译通过

后序写 _1 和_2 的方式才是正确的,尽管 此时 的  placeholders::_1, 和 placeholders::_2 分别对应的是 第二个参数 和 第三个参数。其实可以理解为,bind函数当中缺省之后的参数就不计算在 参数列表当中了。

其实上述的理解要从调用 plus1 和 plus2 理解:

    cout << plus1(10, 5) << endl;
    cout << plus2(10, 5) << endl;

在调用函数的角度看来,不就是第一个参数和第二个参数吗?

 而且,如果我们想要缺省的参数是在 参数列表的中间位置,左右两边都有参数的话,从调用函数的角度来说更好一点:

总结一下:如果不管 bind() 出来的新函数其中有没有 bind 的缺省参数,对于 placeholders::_?指定哪一个参数,要看调用的时候是几个参数,那么对应的 _?  就是 调用函数时候对应的 第 ? 个参数。


看到这个你应该明白了bind 函数是如何使用 缺省参数的,其实bind ()函数实现的缺省参数在大体上看来要比 直接在函数参数列表当中写缺省值要好的。

因为,bind()函数生成的新函数的缺省参数是在 原本函数当中直接传值,也就是在原本的函数当中的被 bind()修改的成缺省参数的参数,本身不是缺省参数。

而且我们可以使用多次 bind 函数()对不同的参数进行缺省;还可以对同一个参数写出不同的缺省值;从而定义多个 function 对象。

 从上述观点来看,bind()的缺省参数是要更灵活一些的。


如果是重载函数想要 使用 bind ()函数的话区分 重载函数,用的是 function的模版参数


如果是类当中 public 的 成员函数,比如是静态函数,那么可以通过 "类名::函数名()" 的方式在类外调用的,那么,在 类当中的 静态成员函数,也是差不多的,"类名::函数名"

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

    function<int(int, int) > plus2 = bind(SubType::Sub, placeholders::_1,placeholders::_2);

 如果是 public 的 非静态的成员函数的话,不能像 "类名::函数名" 一样访问了。需要在之前加一个 "&" ,变成:"&类名::函数名":

 而且,非静态的成员函数和 静态成员函数不一样的是,虽然我们在该函数的参数列表当中写的 是 N 的参数,但是实际上是 N+1 个参数,因为非静态的成员函数的参数列表当中的 第一个参数不是我们在函数参数列表当中写的第一个参数,而且是指向当中对象的 this 指针。

 其实也是和之前保持一致的,我们想要调用 类当中的非静态的成员方法,就需要一个对象作为媒介,所以,调用其中的成员函数需要创建一个对象之后,传入这个对象的指针。

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

int main()
{
    SubType st;
    function<int(int, int) > plus2 = bind(&SubType::Sub, &st, placeholders::_1,placeholders::_2);

    return 0;
}

或者是传入一个匿名对象也行;   不传入一个对象指针,传入一个对戏那个也行。

  之所以支持这样的操作,是因为,不管是 lambda,bind()其实底层都是仿函数,在仿函数类当中要重写 operator()这个函数,上述不管是 &st   ,st  , 还是匿名对象,都是传给这个 operator()函数的。

    function<int(int, int) > plus2 = bind(&SubType::Sub, Subtype() , placeholders::_1,placeholders::_2);

    function<int(int, int) > plus2 = bind(&SubType::Sub, st , placeholders::_1,placeholders::_2);

静态的可以不加,但是也可以加上"&"。

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

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

相关文章

关于SSA算法中矩阵的3种运算:转置、求逆、相乘

1、矩阵转置 // 矩阵转置(二维矩阵)public static double[][] transposeTwo(double[][] matrix) {int rows matrix.length;int cols matrix[0].length;double[][] transposedMatrix new double[cols][rows];for (int i 0; i < rows; i) {for (int j 0; j < cols; j)…

一篇博客带你学会关于Vuex使用

前言&#xff1a; 在我们之前我们的学习vue知识中&#xff0c;我们组件传递方式有父传子&#xff0c;子传父&#xff0c;这两种方法传递参数复杂&#xff0c;所以我们今天来学习Vuex&#xff0c;就可以解决这个传递的问题&#xff01;&#xff01;&#xff01; 一&#xff0c;V…

【算法思考】端到端实例分割模型 SOLO

目录 背景工作总结 背景 不同于语义分割&#xff0c;实例分割不仅需要输出图像的语义蒙版&#xff0c;还要对图像中不同的实例作区分。如下图所示&#xff0c;实例分割任务需要多不同的羊做区分&#xff0c;输出不同的实例蒙版。 由于图片中实例个数的不确定性&#xff0c;实例…

第二章 C++的输出

系列文章目录 第一章 C的输入 文章目录 系列文章目录前言一、个人名片二、cout三、printf总结 前言 今天来学C的输出吧&#xff01; 一、个人名片 二、cout cout 三、printf printf 总结 最近懒得写博客怎么办&#xff1f;

【技术研究】环境可控型原子力显微镜超高真空度精密控制解决方案

摘要&#xff1a;针对原子力显微镜对真空度和气氛环境精密控制要求&#xff0c;本文提出了精密控制解决方案。解决方案基于闭环动态平衡法&#xff0c;在低真空控制时采用恒定进气流量并调节排气流量的方法&#xff0c;在高真空和超高真空控制时则采用恒定排气流量并调节进气流…

Docker系列--网络的配置

原文网址&#xff1a;Docker系列--网络的配置_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Docker的网络的配置。 官网网址 https://docs.docker.com/engine/reference/commandline/network/ 网络的默认设置 Docker启动之后&#xff0c;系统中会产生一个名为docker0的…

2015架构真题(五十)

供应链中信息流覆盖了供应商、制造商和分销商&#xff0c;信息流分为需求信息流和供应信息流&#xff0c;&#xff08;&#xff09;属于需求信息流&#xff0c;&#xff08;&#xff09;属于供应信息流。 库存记录生产计划商品入库单提货发运单 客户订单采购合同完工报告单销售…

Leetcode.2867 统计树中的合法路径数目

题目链接 Leetcode.2867 统计树中的合法路径数目 rating : 2428 题目描述 给你一棵 n n n 个节点的无向树&#xff0c;节点编号为 1 1 1 到 n n n 。给你一个整数 n n n 和一个长度为 n − 1 n - 1 n−1 的二维整数数组 e d g e s edges edges &#xff0c;其中 e d g …

Matlab地理信息绘图—风玫瑰图

文章目录 风玫瑰图的作用Matlab代码实现结果展示 风玫瑰图的作用 风玫瑰图&#xff08;Wind Rose Plot&#xff09;是一种用于可视化风向和风速分布的图表。它通常以极坐标形式呈现&#xff0c;其中角度表示风向&#xff0c;半径表示风速的频率或相对概率。风玫瑰图对于理解和呈…

项目log日志mysql记录,熟悉python的orm框架

直接在项目里面创建一个class&#xff0c;这个类对应着mysql里面的表 我们运行项目&#xff0c;可以自动建立表 在.env中找到mysql的配置信息&#xff0c;这个是在NB服务器上运行的mysql&#xff0c;localhost需要变成NB服务器的ipv4地址 使用Mysql工具连接查看&#xff0c;连…

【Java学习之道】继承与多态

引言 本文将介绍面向对象编程的核心概念——继承与多态。对于初学者来说&#xff0c;掌握这些基本概念是迈向Java高手的第一步。接下来&#xff0c;让我们一起揭开继承与多态的神秘面纱&#xff0c;感受它们的魅力吧&#xff01; 一、继承 继承是面向对象编程的一个重要特性…

C++学习day5

目录 作业&#xff1a; 1> 思维导图 2> 多继承代码实现沙发床 1>思维导图 2> 多继承代码实现沙发床 #include <iostream>using namespace std; //创建沙发类 class sofa { private:string sitting; public:sofa(){cout << "sofa的无参构造函数…

算法leetcode|82. 删除排序链表中的重复元素 II(rust重拳出击)

文章目录 82. 删除排序链表中的重复元素 II&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 82. 删除排序链表中的重复元素 II&#xff1…

开发者职场“生存状态”大调研报告分析 - 第一版

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

三轮技术面 +HR 面,字节跳动面试也不过如此......

前言 人人都有大厂梦&#xff0c;对于软件测试人员来说&#xff0c;BAT 为首的一线互联网公司肯定是自己的心仪对象&#xff0c;毕竟能到这些大厂工作&#xff0c;不仅薪资高待遇好&#xff0c;而且能力技术都能够得到提升&#xff0c;最关键的是还能够给自己镀上一层金&#…

Unity中Shader的光照衰减

文章目录 前言一、衰减原理1、使用一张黑白渐变贴图用于纹理采样2、把模型从世界坐标转化为灯光坐标&#xff08;即以灯光为原点的坐标系&#xff09;3、用转化后的模型坐标&#xff0c;对黑白渐变纹理进行纹理采样4、最后&#xff0c;把采样后的结果与光照模型公式的结果相乘输…

PBA.客户需求分析管理

1 需求的三个层次: Requirement/Wants/Pains 大部分人认为&#xff0c;产品满足不了客户需要&#xff0c;是因为客户告知的需求是错误的&#xff0c;这听起来有一些道理&#xff0c;却没有任何意义。不同角色对于需求的理解是不一样的。在客户的需求和厂家的需求之间必然有一定…

分布式文件服务器——Windows环境MinIO的三种部署模式

上节简单聊到MinIO&#xff1a;分布式文件存储服务——初识MinIO-CSDN博客&#xff0c;但没具化&#xff0c;本节开始展开在Windows环境下 MinIO的三种部署模式&#xff1a;单机单节点、单机纠删码、集群模式。 部署的几种模式简要概括 所谓单机单节点模式&#xff1a;即MinI…

什么是谐波?谐波的危害

一、什么是谐波&#xff1f; “谐波”一词起源于声学。有关谐波的数学分析在18世纪和19世纪已经奠定了良好的基础。傅里叶等人提出的谐波分析方法至今仍被广泛应用。电力系统的谐波问题早在20世纪20年代和30年代就引起了人们的注意。当时在德国&#xff0c;由于使用静止汞弧变流…

PCL源码分析:直通滤波

文章目录 一、简介二、源码分析三、小结参考资料一、简介 让我们从一个最简单的功能开始慢慢重新认识PCL~~,虽然这个功能很简单,但是已可以从中管中窥豹来更加深入了解PCL的内部结构。 二、源码分析 在真正看PCL的源代码之前,我们先简单的看一下直通滤波这个类的类关系: 这…