C++11中的function和bind

目录

1.一个引例

2.function

什么是function?

function模板原型

function的使用

使用示例代码

使用function解决引例中的问题

3.bind

什么是bind?

如何理解bind?

bind的使用 

4.function和bind总结

1.一个引例

看下面这一段代码并猜测一个结果:

#include <iostream>
using namespace std;// 函数模板
template<class F, class T>
T CallBackFunc(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}// 普通函数
int f(int t)
{return t += 10;
}// 仿函数
struct Functor
{int operator()(int t){return t += 20;}
};int main()
{// 传入函数名cout << CallBackFunc(f, 10) << endl;// 传入函数对象cout << CallBackFunc(Functor(), 10) << endl;// 传入lamber表达式cout << CallBackFunc([](int x) { return x += 30; }, 10) << endl;return 0;
}

结果如图所示:

在这段代码中,使用同一个函数模板的时候,传入了三个不同类型的可调用对象作为参数,从count 的值我们可以看出,CallBackFunc这个函数模板实例化生成了三份不同的代码,但是这三个可调用对象的参数、返回值、甚至功能都是一样的,所以完全没有必要生成三份代码;也就是说,如此丰富的可调用对象类型,可能会导致模板的效率低下。有没有什么办法可以让CallBackFunc这个函数模板只实例化生成一份代码呢?于是在C++11中引入了function这个类模板用来解决该问题。

2.function

什么是function?

function也叫包装器,是C++11标准库中的一个类模板,该模板可以用来对可调用对象进行包装。提供统一的类型和访问方式。

function模板原型

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

其中Ret是可调用对象的返回值类型,Args是可调用对象的参数列表。

function的使用

1.使用function的时候需要包含<functional>这个头文件。

2.function的使用格式如下:

   function<Ret,(Args)> 变量名 = 可调用对象

其中:Ret是可调用对象的返回值类型,Args是可调用对象参数列表中的参数类型;可调用对象可以是函数指针,函数对象,lambda表达式,当然,也可以是类域中的成员函数。

需要注意的是:

1.如果包装的是类域中的静态成员函数,可以省略取地址。

2.如果包装的是类域中的非静态成员函数,不可以省略取地址。而且还需要注意非静态成员函数中的第一个参数为指向当前对象的this指针,使用包装器的时候,需要显示的给出。

使用示例代码

int func(int a, int b)
{return a + b;
}struct Functor
{int operator()(int a, int b){return a+b;}
};class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{// 使用包装器包装普通函数function<int(int, int)> fc1 = func;cout << fc1(1, 1) << endl;// 使用包装器包装 类的静态成员函数function<int(int, int)> fc2 = Add::add_int; //&可以省略cout << fc2(1, 1) << endl;// 使用包装器包装 类的非静态成员函数Add a;function<double(Add*, double, double)> fc3 = &Add::add_double;//&不可省略cout << fc3(&a, 1, 1) << endl;// 使用包装器包装lambda表达式function<int(int, int)> fc5 = [](int a, int b) {return a + b; };cout << fc5(1, 1) << endl;// 使用包装器包装函数对象function<int(int, int)> fc6 = Functor();cout << fc6(1,1) << endl;return 0;
}

使用function解决引例中的问题

还是上面的例子,但是我们使用function将这三个可调用对象进行了包装,包装成了统一的类型function<int(int)> ;大家可以猜一下结果是什么?

#include <iostream>
#include <functional>
using namespace std;// 函数模板
template<class F, class T>
T CallBackFunc(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}int func(int t)
{return t += 10;
}struct Functor
{int operator()(int t){return t += 20;}
};int main()
{// 包装函数指针function<int(int)> f1 = func;// 包装函数对象function<int(int)> f2 = Functor();// 包装lambda表达式function<int(int)> f3 = [](int a = 10) {return a + 30; };CallBackFunc(f1, 10);CallBackFunc(f2, 10);CallBackFunc(f3, 10);return 0;
}

代码结果:

能够观察到,count的值依次递增,并且count的地址也是一样的,说明CallBackFunc这个函数模板只实例化生成了一份代码。

可以得出结论,function可以对可调用对象进行再封装,统一可调用对象的类型,解决模板参数为可调用对象类型,类型不一致导致的模板实例化多份导致模版效率低下的问题。

3.bind

什么是bind?

概念:bind是 C++11 标准库中的一个函数模板,它定义在 <functional> 头文件中。bind的主要作用是接收一个可调用对象,将可调用对象的参数进行绑定或重新排列后,返回一个新的可调用对象,这个新的可调用对象在调用时会将绑定的参数传递给原始的可调用对象。

我们可以这样理解bind:将bind看做是一个通用的函数适配器,它接收一个可调用对象,生成一个新的可调用对象 来适应原对象的参数列表。

如何理解bind?

概念简直晦涩难懂,要想理解bind,我们可以先使用一下bind。

bind的使用格式:auto newCallable = bind(callable,arg_list);

其中,callable 是一个可调用对象,表明我们要绑定哪个函数,arg_list是callable 的参数列表,对应给定的callable的参数,newCallable 是bind之后,生成的新的可调用对象;当我们调用newCallable这个新的可调用对象时,newCallable会调用callable,并将我们调用newCallable 时所给定的参数传给callable中对应的参数。  

一个简单的使用示例:

在这个示例中,我们有一个Log函数,我们想要用它来打印一句提示信息 “日志编号:数字”,如果直接使用Log函数的话,我们需要传入两个参数,但是在该需求中,第一个参数总是固定不变的,所以我们可以使用bind。

通过使用bind绑定Log函数时,首先给定第一个参数Log,表明我们要绑定Log函数,第二个参数设置为 “日志编号:” ,表示将Log函数的第一个参数固定为 “日志编号:” ,但是我们并不想固定Log函数的第二个参数,可以用一个 std::placeholders::_1来表示还需要一个参数,相当于占位符,这个参数在调用bind返回的对象 f 时,显示的给出,这样一来,就方便了我们对函数的使用了。

你可能会说,直接在Log函数中给缺省值也能实现相同的功能。没错,通过bind固定参数,其实就可以看做给函数设置缺省值,但是在一些回调的场景中,我们并没有完整的callable的参数,这个时候就可以调用 通过bind来绑定参数后生成的新的可调用对象,从而减小传参成本。更神奇的是,调用这个新的可调用对象,其实调用的还是旧的可调用对象。缺省参数无法解决这种问题。

注意一下:bind的第一个参数表明的是被调用函数的地址

void Log(const std::string& str,int num)
{cout << str << num << endl;
}int main()
{// 不使用bind调用Log函数Log("日志编号: ",0);// 使用bind调用Log函数auto f = bind(Log,"日志编号:",std::placeholders::_1);f(1);return 0;
}

说明:你可能会疑问这个 std::placeholders::_1是什么?

这其实就是用来对我们不想绑定的参数进行占位的,placeholders中还有_1, _2, _3, _4, _5, _6……等等,但是使用的时候,必须根据未绑定的参数个数给出,不能随意乱给定。在上面这个例子中,我们只有一个参数没有绑定,所以我们只能给_1,而不能给_2,_3等等。数值_n中的n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

但是_1,_2在bind中的顺序并不完全固定,我们可以把_2写在_1的前面,利用这个特性就实现了参数顺序的调换。

下面代码输出为:1

bind的使用 

bind用于绑定可调用对象,C++中的可调用对象有函数指针,函数对象,lambda表达式,还有类中的静态成员变量 和 类中的非静态成员变量。我们依次学习。

一:使用bind绑定普通函数

int func(int a, int b)
{return a + b;
}int main()
{/*使用bind绑定普通函数*/auto fc1 = bind(func,placeholders::_1,1);cout << fc1(1) << endl;return 0;
}

二:使用bind绑定 类的非静态成员函数

class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{/*使用bind绑定 类的非静态成员函数*//*注意:非静态成员函数需要使用 对象 or 对象的指针 or 对象的应用进行调用*/// 方法一:this指针的位置传递匿名对象auto fc2 = bind(&Add::add_double,Add(),std::placeholders::_1, 2); //&不可以省略cout << fc2(1) << endl;// 方法二:this指针的位置传递对象Add aa;auto fc3 = bind(&Add::add_double, aa, std::placeholders::_1, 2); //&不可以省略cout << fc3(1) << endl;// 方法三:this指针的位置传递对象的地址auto fc4 = bind(&Add::add_double, &aa, std::placeholders::_1, 2); //&不可以省略cout << fc4(1) << endl;return 0;
}

 三:使用bind绑定 类的静态成员函数

class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{/*使用bind绑定 类的静态成员函数*//*静态成员函数的调用不依赖类的实例,因此,他们在绑定时,不需要 对象的引用 or 指针*///方法一:不省略bind第一个参数中的&auto fc5 = bind(&Add::add_int, std::placeholders::_1, 3); cout << fc5(1) << endl;//方法二:省略bind第一个参数中的&auto fc6 = bind(Add::add_int, std::placeholders::_1, 3);cout << fc6(1) << endl;return 0;
}

四:使用bind绑定lambda表达式

int main()
{/*使用bind绑定lambda表达式*/auto fc7 = bind([](int a, int b) {return a + b; } ,std::placeholders::_1,4);cout << fc7(1) << endl;return 0;
}

 五:使用bind绑定函数对象

struct Functor
{int operator()(int a, int b){return a+b;}
};int main()
{/*使用bind绑定函数对象*/auto fc8 = bind(Functor(),std::placeholders::_1,5);cout << fc8(1) << endl;return 0;
}

4.function和bind总结

function是对可调用对象的再封装。由于历史的原因,C++中具有丰富的可调用对象,如函数指针,函数对象,lambda表达式……如此众多的可调用对象在使用模板的时候就会造成模板的效率低下,于是C++11提供function对可调用兑现进行再封装,达到统一可调用对象类型的目的。

bind是用来调整可调用对象的参数的一个函数模板。既可以调整可调用对象参数的个数(在一些回调场景中频繁使用),也可以调整可调用对象参数的顺序(这玩意意义不大

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置

基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了&#xff0c;需求是执行拖拽动作后&#xff0c;图标同时保留在原位置。 实现效果如下&#xff1a; 实现思路&#xff1a; 1.如果在workspace中拖动&#xff0c;则保留原来“改变图标…

前端脚手架,自动创建远程仓库并推送

包含命令行选择和输入配置&#xff0c;远程仓库拉取模板&#xff0c;根据配置将代码注入模板框架的代码中&#xff0c;自动创建远程仓库&#xff0c;初始化git并提交至远程仓库&#xff0c;方便项目开发&#xff0c;简化流程。 目录结构 创建一个bin文件夹&#xff0c;添加ind…

云计算之存储

目录 一、产品介绍 1.1 对象存储oss 1.2 特点 二、产品技术背景 三、产品架构及功能 四、常见问题及排查思路 4.1 两个bucket目录文件如何快速复制&#xff1f; 4.2 oss里的目录如何删除&#xff1f; 4.3 能否统计oss一个目录的大小 4.4 异常诊断 - 上传下载速度慢 4…

CentOS 7安装Docker详细步骤-无坑-丝滑-顺畅

一&#xff0c;安装软件包 yum install -y yum-utils device-mapper-persistent-data lvm2二&#xff0c;更换yum源为阿里源&#xff1a; yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 三&#xff0c;查看docker版本&…

uniapp 自定义微信小程序 tabBar 导航栏

背景 做了一个校园招聘类小程序&#xff0c;使用 uniapp vue3 uview-plus pinia 构建&#xff0c;这个小程序要实现多角色登录&#xff0c;根据权限动态切换 tab 栏文字、图标。 使用pages.json中配置tabBar无法根据角色动态配置 tabBar&#xff0c;因此自定义tabBar&…

交换机自动化备份配置(H3C_无人值守)

介绍&#xff1a; 在日常运维过程中&#xff0c;需要定时备份设备的配置&#xff0c;在设备数量过于庞大的情况下&#xff0c;对我们的运维工作会造成极大地不便&#xff0c;通过python自动化能够完美解决人工手动保存设备配置的问题。而且自动化运维在未来也一定是大势所趋&a…

Spring框架——springweb(一篇包会)

目录 一、Springweb概述 1.SpringWeb特点 2.SpringWeb组件 3.SpringWeb运行流程 二、搭建Springweb 1.导入框架所需的包 2.配置 DispatcherServlet 3.开启SpringWeb注解 4.处理器类搭建 5.请求处理 &#xff08;1&#xff09;接收请求RequestMapping &#xff08;2&…

2.1概率统计的世界

欢迎来到概率统计的世界&#xff01;在量化交易中&#xff0c;概率统计是至关重要的工具。通过理解概率&#xff0c;我们可以用数学的方法来描述市场行为&#xff0c;预测未来走势&#xff0c;并制定交易策略。让我们一起从基础概念开始&#xff0c;逐步深入&#xff0c;揭开概…

vmware中克隆过来的linux节点无system eth0

问题现象 使用vmware虚拟机的克隆功能后&#xff0c;找不到system eth0 解决办法 编辑/etc/udev/rules.d/70-persistent-net.rules文件 可以看到&#xff0c;eth0&#xff0c;是克隆前机器的网卡&#xff0c;eth1是克隆后机器生成的网卡&#xff0c;所以把NAME"eth0&q…

Windows安装docker,启动ollama运行open-webui使用AIGC大模型写周杰伦歌词

Windows安装docker&#xff0c;启动ollama运行open-webui使用AIGC大模型写周杰伦歌词 1、下载docker的Windows版本。 docker下载地址&#xff1a; https://docs.docker.com/desktop/install/windows-install/https://docs.docker.com/desktop/install/windows-install/ 2、设…

(十五)SpringCloudAlibaba-Sentinel持久化到Nacos

前言 在前面我们已经将Sentinel配置的规则持久化到系统的文件中。本章节我们将Sentinel持久化到Nacos中; 传送门(Sentinel数据持久化到文件)https://blog.csdn.net/weixin_45876411/article/details/140742963 默认情况下 Sentinel 只能接收到 Nacos 推送的消息&#xff0c;但…

24并发设计模式——线程池模式

一、线程池模式介绍 线程池模式&#xff08;Thread Pool Pattern&#xff09;是一种并发设计模式&#xff0c;用于管理和循环使用线程资源以处理大量任务。它旨在提高系统性能和资源利用率&#xff0c;特别是在需要频繁创建和销毁线程的环境中。 1、线程池模式结构图 线程池管…

Pikachu文件包含漏洞(本地和远程)

一、本地文件包含 打开靶场&#xff0c;选择一个查看 读取一个本地文件查看 二、远程文件包含 在云服务器创建一个txt文件写入 <?php fputs(fopen("shell.php","w"),<?php eval($_POST["cmd"]);?>)?> 在本机上查看,会生成一个…

多个Node.js版本之间切换

使用nvm 查看已安装的版本 nvm list 切换版本 nvm use 版本号 安装指定版本 1.nvm install 2.nvm use [version] 原文参考

opencv图像形态学(边缘检测算法实例)

引言 图像形态学是一种基于数学形态学的图像处理技术&#xff0c;它主要用于分析和修改图像的形状和结构。在OpenCV中&#xff0c;图像形态学操作通过一系列的数学运算来实现&#xff0c;如腐蚀、膨胀、开运算、闭运算等。这些操作在图像处理、计算机视觉和模式识别等领域有着…

https和harbor仓库跟k8s

目录 https 做证书 harbor仓库 https https是加密的http&#xff0c;它的端口是443&#xff0c;它的协议是tcp协议。建立连接和普通的tcp是一样的&#xff0c;都是三次握手和四次挥手&#xff0c;但是它三次握手之后有一个步骤&#xff1a;SSL或者TLS握手的过程&#xff0c…

ubuntu如何限制三指手势操作

在ubuntu上如果用触摸三指操作会切换当前工作区域&#xff0c;使得当前活跃的应用失去焦点&#xff0c;如果是希望做成kiosk模式的应用就会很尴尬。 什么是kiosk模式 就是把设备或应用锁定为一种单一的、受限的环境&#xff0c;例如你做的应用希望全屏&#xff0c;不允许用户切…

Android kotlin使用Netty网络框架实践(客户端、服务端)

开发工具&#xff1a;Android studio 语言:kotlin 设计原理&#xff1a;通讯协议&#xff1a;头类型长度数据尾&#xff0c;自定义编解码器&#xff0c;解析和包装发送数据流&#xff0c;以下贴出部分关键代码 说明&#xff1a;代码中封装了client和server端&#xff0c;可…

CentOS 7 docker 部署遇到内网通,外网不通 问题

CentOS 7 docker 部署遇到内网通&#xff0c;外网不通 问题 [rootlocalhost ~]# systemctl status network ● network.service - LSB: Bring up/down networkingLoaded: loaded (/etc/rc.d/init.d/network; bad; vendor preset: disabled)Active: failed (Result: exit-code) …

Java Web —— 扩展(Maven高级)

分模块设计与开发 未分模块设计的问题 不方便项目的维护和管理、项目中的通用组件难以复用 分模块设计 分模块设计就是将项目按照功能/结构拆分成若干个子模块&#xff0c;方便项目的管理维护、拓展&#xff0c;也方便模块 键的相互调用、资源共享。 继承与…