c++11新特性篇-可调用对象包装器, 绑定器
可调用对象包装器, 绑定器
可调用对象
可调用对象是指在 C++ 中能够像函数一样被调用的实体。它包括了多种类型的对象,使得它们能够像函数一样被调用,可以是函数、函数指针、函数对象、Lambda 表达式等。在C++中,具有以下特征之一的实体都被认为是可调用对象:
1.函数: 常规的函数,包括全局函数和类的成员函数。
void regularFunction(int x) {// ...
}
2.函数指针: 指向函数的指针。
#include <iostream>// 函数原型
void sayHello(const char* name) {std::cout << "Hello, " << name << "!" << std::endl;
}int main() {// 定义函数指针并初始化为指向 sayHello 函数void (*helloFunctionPointer)(const char*) = sayHello;// 使用函数指针调用函数helloFunctionPointer("Alice");helloFunctionPointer("Bob");return 0;
}
3.函数对象: 重载了函数调用运算符 operator() 的类。(仿函数)
#include <iostream>// 函数对象类
struct Functor {int operator()(int x, int y) {return x + y;}
};int main() {// 创建函数对象的实例Functor adder;// 使用函数对象int result = adder(3, 4);std::cout << "Result: " << result << std::endl;return 0;
}
4.Lambda表达式: 匿名的函数对象。
#include <iostream>int main() {// Lambda 表达式,接受一个整数参数并返回它的平方auto square = [](int x) {return x * x;};// 调用 Lambda 表达式int result = square(5);std::cout << "Square of 5 is: " << result << std::endl;return 0;
}
5.类的成员函数指针: 指向类的成员函数或对象的指针。
#include <iostream>
#include <string>
#include <vector>
using namespace std;struct Test
{void print(int a, string b){cout << "name: " << b << ", age: " << a << endl;}int m_num;
};int main(void)
{// 定义类成员函数指针指向类成员函数void (Test::*func_ptr)(int, string) = &Test::print;// 类成员指针指向类成员变量int Test::*obj_ptr = &Test::m_num;Test t;// 通过类成员函数指针调用类成员函数(t.*func_ptr)(19, "Monkey D. Luffy");// 通过类成员指针初始化类成员变量t.*obj_ptr = 1;cout << "number is: " << t.m_num << endl;return 0;
}
在上面的例子中满足条件的这些可调用对象对应的类型被统称为可调用类型。C++中的可调用类型虽然具有比较统一的操作形式,但定义方式五花八门,这样在我们试图使用统一的方式保存,或者传递一个可调用对象时会十分繁琐。现在,C++11通过提供std::function 和 std::bind统一了可调用对象的各种操作。
可调用对象包装器
std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。
基本用法
std::function必须要包含一个叫做functional的头文件,可调用对象包装器使用语法如下:
#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;
下面的实例代码中演示了可调用对象包装器的基本使用方法:
#include <iostream>
#include <functional>
using namespace std;int add(int a, int b)
{cout << a << " + " << b << " = " << a + b << endl;return a + b;
}class T1
{
public:static int sub(int a, int b){cout << a << " - " << b << " = " << a - b << endl;return a - b;}
};class T2
{
public:int operator()(int a, int b){cout << a << " * " << b << " = " << a * b << endl;return a * b;}
};int main(void)
{// 绑定一个普通函数function<int(int, int)> f1 = add;// 绑定以静态类成员函数function<int(int, int)> f2 = T1::sub;// 绑定一个仿函数T2 t;function<int(int, int)> f3 = t;// 函数调用f1(9, 3);f2(9, 3);f3(9, 3);return 0;
}
输入结果如下:
9 + 3 = 12
9 - 3 = 6
9 * 3 = 27
通过测试代码可以得到结论:std::function可以将可调用对象进行包装,得到一个统一的格式,包装完成得到的对象相当于一个函数指针,和函数指针的使用方式相同,通过包装器对象就可以完成对包装的函数的调用了。
作为回调函数使用
因为回调函数本身就是通过函数指针实现的,使用对象包装器可以取代函数指针的作用,来看一下下面的例子:
#include <iostream>
#include <functional>
using namespace std;// 模拟某个异步操作
void performAsyncOperation(function<void()> callback) {// 模拟异步操作完成后调用回调函数std::cout << "Async operation completed." << std::endl;callback();
}// 回调函数1
void callback1() {std::cout << "Callback 1 called." << std::endl;
}// 回调函数2
void callback2() {std::cout << "Callback 2 called." << std::endl;
}int main() {// 使用 function 作为回调函数的容器function<void()> callbackFunc1 = callback1;function<void()> callbackFunc2 = callback2;// 执行异步操作,传递回调函数performAsyncOperation(callbackFunc1);// 执行异步操作,传递另一个回调函数performAsyncOperation(callbackFunc2);return 0;
}
在这个例子中,CallbackFunction 是一个 std::function 类型,用于表示无返回值且无参数的回调函数。performAsyncOperation 函数模拟了一个异步操作,完成后调用传递的回调函数。在 main 函数中,创建了两个不同的回调函数,然后分别传递给 performAsyncOperation 函数。
绑定器
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:
- 将可调用对象与其参数一起绑定成一个仿函数。
- 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。
绑定器函数使用语法格式如下:
// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);
先来看两个简单例子:
#include <functional>
#include <iostream>void functionToBind(int a, int b) {std::cout << "Sum: " << a + b << std::endl;
}int main() {// 使用 std::bind 绑定函数和参数auto boundFunction = std::bind(functionToBind, 10, std::placeholders::_1);// 调用绑定的函数boundFunction(20); // 输出 "Sum: 30"return 0;
}
在这个例子中,std::bind 绑定了 functionToBind 函数,并将第一个参数绑定为 10,第二个参数使用 std::placeholders::_1 占位符,表示稍后提供的参数。然后,通过调用 boundFunction(20),实际提供了一个参数 20,而 std::placeholders::_1 被替换为 20,最终调用了 functionToBind(10, 20)。
#include <functional>
#include <iostream>class MyClass {
public:void memberFunction(int a, int b) {std::cout << "Sum: " << a + b << std::endl;}
};int main() {MyClass obj;// 使用 std::bind 绑定成员函数和对象auto boundMemberFunction = std::bind(&MyClass::memberFunction, &obj, 10, std::placeholders::_1);// 调用绑定的成员函数boundMemberFunction(20); // 输出 "Sum: 30"return 0;
}
这个例子演示了如何使用 std::bind 绑定类的成员函数。注意,需要提供对象的地址作为第一个参数 , &obj 为第二个参数。后续的参数和占位符的使用方式与前面的例子类似。
在上面的程序中,使用了std::bind绑定器,在函数外部通过绑定不同的函数,控制了最后执行的结果。std::bind绑定器返回的是一个仿函数类型,得到的返回值可以直接赋值给一个std::function,在使用的时候我们并不需要关心绑定器的返回值类型,使用auto进行自动类型推导就可以了。
placeholders::_1是一个占位符,代表这个位置将在函数调用时被传入的第一个参数所替代。同样还有其他的占位符placeholders::_2、placeholders::_3、placeholders::_4、placeholders::_5等……
有了占位符的概念之后,使得std::bind的使用变得非常灵活:
#include <iostream>
#include <functional>
using namespace std;void output(int x, int y)
{cout << x << " " << y << endl;
}int main(void)
{// 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数bind(output, 1, 2)();bind(output, placeholders::_1, 2)(10);bind(output, 2, placeholders::_1)(10);// error, 调用时没有第二个参数// bind(output, 2, placeholders::_2)(10);// 调用时第一个参数10被吞掉了,没有被使用bind(output, 2, placeholders::_2)(10, 20);function<void(int, int)> func1 = bind(output, placeholders::_1, placeholders::_2);auto func2 = bind(output, placeholders::_2, placeholders::_1);func1(10, 20);func2(10, 20);return 0;
}
示例代码执行的结果:
1 2 // bind(output, 1, 2)();
10 2 // bind(output, placeholders::_1, 2)(10);
2 10 // bind(output, 2, placeholders::_1)(10);
2 20 // bind(output, 2, placeholders::_2)(10, 20);
10 20 // bind(output, placeholders::_1, placeholders::_2)(10, 20);
20 10 // bind(output, placeholders::_2, placeholders::_1)(10, 20);
通过测试可以看到,std::bind可以直接绑定函数的所有参数,也可以仅绑定部分参数。在绑定部分参数的时候,通过使用std::placeholders来决定空位参数将会属于调用发生时的第几个参数。
可调用对象包装器std::function是不能实现对类成员函数指针或者类成员指针的包装的,但是通过绑定器std::bind的配合之后,就可以完美的解决这个问题了,再来看一个例子,然后再解释里边的细节:
#include <iostream>
#include <functional>
using namespace std;class Test
{
public:void output(int x, int y){cout << "x: " << x << ", y: " << y << endl;}int m_number = 100;
};int main(void)
{Test t;// 绑定类成员函数function<void(int, int)> f1 = bind(&Test::output, &t, placeholders::_1, placeholders::_2);// 绑定类成员变量(公共)function<int&(void)> f2 = bind(&Test::m_number, &t);// 调用f1(520, 1314);f2() = 2333;cout << "t.m_number: " << t.m_number << endl;return 0;
}
示例代码输出的结果:
x: 520, y: 1314
t.m_number: 2333
在用绑定器绑定类成员函数或者成员变量的时候需要将它们所属的实例对象一并传递到绑定器函数内部。f1的类型是function<void(int, int)>,通过使用std::bind将Test的成员函数output的地址和对象t绑定,并转化为一个仿函数并存储到对象f1中。
使用绑定器绑定的类成员变量m_number得到的仿函数被存储到了类型为function<int&(void)>的包装器对象f2中,并且可以在需要的时候修改这个成员。其中int是绑定的类成员的类型,并且允许修改绑定的变量,因此需要指定为变量的引用,由于没有参数因此参数列表指定为void。
示例程序中是使用function包装器保存了bind返回的仿函数,如果不知道包装器的模板类型如何指定,可以直接使用auto进行类型的自动推导,这样使用起来会更容易一些。
相关文章:
c++11新特性篇-可调用对象包装器, 绑定器
可调用对象包装器, 绑定器 可调用对象 可调用对象是指在 C 中能够像函数一样被调用的实体。它包括了多种类型的对象,使得它们能够像函数一样被调用,可以是函数、函数指针、函数对象、Lambda 表达式等。在C中,具有以下特征之一的实体都被认为…...
论文阅读:“Appearance Capture and Modeling of Human Teeth”
文章目录 AbstractIntroductionMethod OverviewTeeth Appearance ModelEnamelDentinGingiva and oral cavity Data AcquisitionImage captureGeometry capture ResultsReferences Abstract 如果要为电影,游戏或其他类型的项目创建在虚拟环境中显示的人类角色&#…...
初学vue3与ts:路由跳转带参数
index-router <!-- 路由跳转 --> <template><div><div class"title-sub flex"><div>1、用router-link跳转带参数id1:</div><router-link to"./link?id1"><button>点我跳转</button>&…...
JAVAEE---多线程
线程安全 这段代码执行结果就就是一个不确定的数,就存在线程安全问题。 为了解决这样的问题我们可以对count进行打包,我们知道count本质上应该是由三个指令完成,我们可以对其打包。 这样的代码结果就是正确的。我们对对象就进行了加锁&#…...
提示工程-Prompt Engineering
提示工程 提示工程 1、概述 Prompt Engineering: 提示工程 通过自然语言(英语、汉语等)来给AI下达指示,从而让AI完成你指定给他的工作的过程都可以称之为提示工程。(面向自然语言编程) 提示词要素 指令&…...
JetLinks设备接入的认识与理解【woodwhales.cn】
为了更好的阅读体验,建议移步至笔者的博客阅读:JetLinks设备接入的认识与理解 1、认识 JetLinks 1.1、官网文档 官网:https://www.jetlinks.cn/ JetLinks 有两个产品:JetLinks-lot和JetLinks-view 官方文档: JetLi…...
机器人开发的选择
喷涂机器人 码垛机器人 纸箱码垛机器人 焊接机器人 跳舞机器人 管道清理机器人 工地巡检机器人 点餐机器人 化工巡检机器人 装箱机器人 安防巡检机器人 迎宾机器人好像有点像软银那个 污水管道检测机器人 大酒店用扫地机器人 家用扫地机器人 工厂用(…...
LeetCode Hot100 102.二叉树的层序遍历
题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 方法:迭代 class Solution {public List<List<Integer>> levelOrder(TreeNode root) {if …...
【Kotlin】类与接口
文章目录 类的定义创建类的实例构造函数主构造函数次构造函数init语句块 数据类的定义数据类定义了componentN方法 继承AnyAny:非空类型的根类型Any?:所有类型的根类型 覆盖方法覆盖属性覆盖 抽象类接口:使用interface关键字函数:funUnit:让…...
Wagtail-基于Python Django的内容管理系统CMS如何实现公网访问
Wagtail-基于Python Django的内容管理系统CMS实现公网访问 文章目录 Wagtail-基于Python Django的内容管理系统CMS实现公网访问前言1. 安装并运行Wagtail1.1 创建并激活虚拟环境 2. 安装cpolar内网穿透工具3. 实现Wagtail公网访问4. 固定的Wagtail公网地址 前言 Wagtail是一个…...
什么是LASSO回归,怎么看懂LASSO回归的结果
随着机器学习的发展,越来越多SCI文章都使用了更多有趣、高效的统计方法来进行分析,LASSO回归就是其中之一。很多小伙伴听说过LASSO,但是对于LASSO是什么,有什么用,怎么才能实现,大家可能一头雾水。今天的文…...
python树长子兄弟链存储结构(孩子兄弟链存储结构)
长子兄弟链存储结构(孩子兄弟链存储结构)解释: 长子兄弟链存储结构是一种树的存储结构,它使用孩子兄弟表示法(也称作左孩子右兄弟表示法)来表示树的结构。这种表示方法主要用于存储一般的树,而不…...
开源和闭源软件对开发的影响
开源软件的优势: 开源性:开源软件允许任何人查看、修改和发布源代码,这促进了代码的共享和集体学习。透明性:开源软件提高了软件的透明度,使用户可以更好地理解软件的工作原理,增加对软件的信任。社区支持…...
centos无法进入系统之原因解决办法集合
前言 可爱的小伙伴们,由于精力有限,暂时整理了两类。如果没有你遇到的问题也没有关系,欢迎底下留言评论或私信,小编看到后第一时间帮助解决 一. Centos 7 LVM xfs文件系统修复 情况1: [sda] Assuming drive cache:…...
【Linux】系统初始化配置
CentOS 7 的虚拟机安装后必须要做的几个操作,记录以下,网络配置修改、yum源安装、基础工具安装: 1、先修改权限,新建普通用户,并授权普通用户apps 的sudo权限; useradd apps password apps visudo apps A…...
使用VC++设计程序对一幅256级灰度图像进行全局固定阈值分割、自适应阈值分割
图像分割–全局固定阈值分割、自适应阈值分割 获取源工程可访问gitee可在此工程的基础上进行学习。 该工程的其他文章: 01- 一元熵值、二维熵值 02- 图像平移变换,图像缩放、图像裁剪、图像对角线镜像以及图像的旋转 03-邻域平均平滑算法、中值滤波算法、…...
【ArcGIS Pro微课1000例】0035:栅格影像拼接(dem高程数据)
本实验讲解在ArcGIS Pro中,栅格数据的两种拼接(镶嵌)方法,适用于遥感影像、DOM、DEM、DSM等常见栅格数据。 文章目录 一、加载实验数据二、栅格拼接工具1. 镶嵌2. 镶嵌至新栅格三、注意事项四、拓展阅读一、加载实验数据 加载配套实验数据中的0035.rar中的两个dem数据,如…...
Zynq-7000系列FPGA使用 Video Processing Subsystem 实现图像缩放,提供工程源码和技术支持
目录 1、前言免责声明 2、相关方案推荐FPGA图像处理方案FPGA图像缩放方案自己写的HLS图像缩放方案 3、设计思路详解Video Processing Subsystem 介绍 4、工程代码详解PL 端 FPGA 逻辑设计PS 端 SDK 软件设计 5、工程移植说明vivado版本不一致处理FPGA型号不一致处理其他注意事项…...
【C】内存函数
目录 1. memcpy 使用和模拟实现 2. memmove 使⽤和模拟实现 3. memset 函数的使用 4. memcmp 函数的使用 1. memcpy 使用和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); • 函数memcpy从source的位置开始向后复制num个字节的数据到d…...
windows系统玩游戏找不到d3dx9_35.dll缺失的解决方法
分享一个我们在打开游戏或许软件过程中遇到的问题——“由于找不到d3dx9_35.dll,无法继续执行代码”的五个修复方案。这个问题可能会影响到我们的工作和娱乐效率,甚至可能导致工作的延期。因此,我希望通过今天的文章,能够帮助大家更好地解决这…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
