C++ - 包装器

news2024/9/27 19:23:31

包装器

在 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/1078553.html

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

相关文章

FaceFusion:探索无限创意,创造独一无二的面孔融合艺术!

FaceFusion&#xff1a;探索无限创意&#xff0c;创造独一无二的面孔融合艺术&#xff01; 它使用先进的图像处理技术&#xff0c;允许用户将不同的面部特征融合在一起&#xff0c;创造有趣和令人印象深刻的效果。这个项目的潜在应用包括娱乐、虚拟化妆和艺术创作&#xff0c;…

GIS 算法原理记录总结二:距离、方位角、沿线上的点的扩展算法及其使用(一)

GIS 算法原理记录总结二&#xff1a;距离、方位角、沿线上的点的扩展算法及其使用&#xff08;一&#xff09; 在了解了距离算法、方位角算法之后&#xff0c;就可以根据距离、方位角进行一些扩展应用。这里罗列如下&#xff1a; 一、计算线段中点&#xff08;turf.midpoint&…

❋JQuery的快速入门2 jq动画与案例

目录 jq自定义动画【animate&#xff0c;stop】 案例1&#xff1a;大小图标 案例2&#xff1a;动态增加删除数据 案例3&#xff1a;动态留言与删除 案例4&#xff1a;动态进度条 案例5&#xff1a;点击三个相同的图片进行消除 jq自定义动画【animate&#xff0c;stop】 a…

艺术字画雕刻经营配送商城小程序的作用是什么

一副传神且精致的绘画/雕塑品不仅具有很好的观赏性&#xff0c;更具备售卖属性&#xff0c;当然由于产品本身本身的局限性&#xff0c;无论开店还是线上朋友圈推广&#xff0c;都难有效果。 通过【雨科】平台搭建字画雕刻经营商城&#xff0c;将所有产品线上售卖&#xff0c;电…

内网渗透面试问题

文章目录 1、熟悉哪些域渗透的手段2、详细说明哈希传递的攻击原理NTLM认证流程哈希传递 3、聊一下黄金票据和白银票据4、shiro反序列化漏洞的形成原因&#xff0c;尝试使用burp抓包查看返回包内容安装环境漏洞验证 5、log4j组件的命令执行漏洞是如何造成的6、画图描述Kerberos协…

vue 组件拖拽vue-slicksort应用

1.引入 import { SlickList, SlickItem, HandleDirective } from vue-slicksort 2.注册组件 components: {SlickList,SlickItem}, 3.应用<slick-listv-model"formData.goods"axis"xy":use-drag-handle"true"input"slickListSort"so…

vue3 添加水印效果

效果图 水印组件 <template><div class"elementdiv" ref"waterMarkRef"><slot></slot></div> </template><script setup> import { ref, onMounted, nextTick } from "vue"; import Watermark from …

Mock工具之Moco使用

一、什么是Mock mock英文单词有愚弄、嘲笑、模拟的意思&#xff0c;这里主要是模拟的意思 二、什么是Moco 开源的、基于java开发的一个mock框架支持http、https、socket等协议 三、Mock的特点 只需要简单的配置request、response等即可满足要求 支持在request 中设置headers、…

【Ceph Block Device】块设备挂载使用

文章目录 前言创建pool创建user创建image列出image检索image信息调整image大小增加image大小减少image大小 删除image从pool中删除image从pool中“延迟删除”image从pool中移除“延迟删除的image” 恢复image恢复指定pool中延迟删除的image恢复并重命名image 映射块设备格式化i…

Rockchip RK3399 - linux下抓取usb数据包

---------------------------------------------------------------------------------------------------------------------------- 开发板 &#xff1a;NanoPC-T4开发板eMMC &#xff1a;16GBLPDDR3 &#xff1a;4GB 显示屏 &#xff1a;15.6英寸HDMI接口显示屏u-boot &…

03黑马店评-添加商户缓存和商户类型的缓存到Redis

商户查询缓存 什么是缓存 实际开发过程中数据量可以达到几千万,缓存可以作为避震器防止过高的数据访问猛冲系统,避免系统内的操作线程无法及时处理信息而瘫痪 缓存(Cache)就是数据交换的缓冲区(储存临时数据的地方),我们俗称的"缓存"实际就是缓冲区内的数据(一般从…

L14D6内核模块编译方法

一、内核模块基础代码解析 一个内核模块代码错误仍然会导致的内核崩溃。 GPL协议&#xff1a;开源规定&#xff0c;使用内核一些函数需要 1、单内核的缺点 单内核扩展性差的缺点减小内核镜像文件体积&#xff0c;一定程度上节省内存资源提高开发效率不能彻底解决稳定性低的缺…

香港鼎鑫鸿鄴:紧跟国家新能源政策,制定合理发展规划

由于全球环境的变化以及环境的需要,世界上的许多资源企业正在努力研究和开发新的、更好的绿色产品和服务,并且取得了一系列的突破和成果——其中最为瞩目的是新能源领域的发展与革新。随着我国经济的快速发展,人民生活水平的不断提高,对清洁能源的需求日益增长,同时也带动了新能…

python 定时器

需求 我想在某一时刻完成某个任务,需要一个定时计划 调研了几种方式都不是很理想. 参考,python实现定时任务的8种方式详解 选择使用 apscheduler 库吧 APScheduler简介 APScheduler是Python的一个定时任务框架&#xff0c;用于执行周期或者定时任务&#xff0c;该框架不仅可以…

【数据结构】二叉树遍历的实现(超详细解析,小白必看系列)

目录 一、前言 &#x1f34e;为何使用链式二叉树 &#x1f350;何为链式二叉树 &#x1f349;二叉树的构建 &#x1f4a6;创建二叉链结构 &#x1f4a6;手动构建一颗树 &#x1f353;二叉树的遍历 &#xff08;重点&#xff09; &#x1f4a6;前序遍历 &#x1f4a6;中…

二维码智慧门牌管理系统:提升小区管理的智能化水平

文章目录 前言一、二维码智慧门牌管理系统简介二、精准的门牌定位三、门牌编号优化三、更多特点四、未来展望 前言 随着科技的不断发展&#xff0c;智能化管理已经深入到我们生活的方方面面&#xff0c;而小区作为我们居住的重要场所&#xff0c;智能化管理更是必不可少。为了…

警惕!外贸常见的一些骗局!

随着网络技术和国际支付的普及&#xff0c;外贸汇款骗局也是时常发生&#xff0c;本文将列举外贸汇款骗局的常见套路和风险提示&#xff0c;以帮助广大外贸人更好地护好自己的“钱袋子”。 常见套路 1.钓鱼 出口商与买家的订单已经谈妥&#xff0c;把收款信息通过邮件发给买家…

【重拾C语言】七、指针(二)指针与数组(用指针标识数组、多维数组与指针、数组指针与指针数组)

目录 前言 七、指针 7.1~3 指针与变量、指针操作、指向指针的指针 7.4 指针与数组 7.4.1 用指针标识数组 7.4.2 应注意的问题 a. 数组名是指针常量 b. 指针变量的当前值 c. 数组超界 7.4.3 多维数组与指针 7.4.4 指针数组 a. 指针数组 b. 数组指针 c. 对比总结 前…

bash上下键选择选项demo脚本

效果如下&#xff1a; 废话不多说&#xff0c;上代码&#xff1a; #!/bin/bashoptions("111" "222" "333" "444") # 选项列表 options_index0 # 默认选中第一个选项 options_len${#options[]}echo "请用上下方向键进行选择&am…

【多线程案例】Java实现简单定时器(Timer)

1.定时器&#xff08;Timer&#xff09; 1.什么是定时器&#xff1f; 在日常生活中,如果我们想要在 t 时间 后去做一件重要的事情,那么为了防止忘记,我们就可以使用闹钟的计时器功能,它会在 t 时间后执行任务&#xff08;响铃&#xff09;提醒我们去执行这件事情. — 这就是J…