C++11特性:可调用对象以及包装器function的使用

在C++中存在“可调用对象”这么一个概念。准确来说,可调用对象有如下几种定义:

是一个函数指针:

int print(int a, double b)
{cout << a << b << endl;return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

是一个具有operator()成员函数的类对象(仿函数): 

#include <iostream>
#include <string>
#include <vector>
using namespace std;struct Test
{// ()操作符重载void operator()(string msg){cout << "msg: " << msg << endl;}
};int main(void)
{Test t;t("我是要成为海贼王的男人!!!");	// 仿函数return 0;
}

是一个可被转换为函数指针的类对象 :

#include <iostream>
#include <string>
#include <vector>
using namespace std;using func_ptr = void(*)(int, string);
struct Test
{static void print(int a, string b){cout << "name: " << b << ", age: " << a << endl;}// 将类对象转换为函数指针operator func_ptr(){return print;}
};int main(void)
{Test t;// 对象转换为函数指针, 并调用t(19, "Monkey D. Luffy");return 0;
}

是一个类成员函数指针或者类成员指针: 

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

关于应该注意到的一些细节都在注释里面了: 

#include<iostream>
using namespace std;
/*1.是一个函数指针2.是一个具有operator()成员函数的类对象(仿函数)3.是一个可被转换为函数指针的类对象4.是一个类成员函数指针或者类成员指针
*///普通函数
void print(int num, string name)
{cout << "id:" << num << ",name:" << name << '\n';
}using funcptr = void(*)(int, string);
//类
class Test
{
public:// 重载void operator()(string msg){cout << "仿函数:" << msg << '\n';}// 将类对象转化为函数指针operator funcptr()// 后面的这个()不需要写任何参数{// 不能返回hello,虽然hello的参数也是int和string,// 但是hello在未示例化之前是不存在的,world是属于类的return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址}void hello(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}static void world(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}int m_id = 520;string m_name = "luffy";
};int main()
{Test t;t("我是要成为海贼王的男人");// 重载被执行Test tt;tt(19, "luffy");// 类的函数指针funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数// 给函数指针加上作用域就可以指向类中的非静态函数了using fptr = void(Test::*)(int, string);fptr f1 = &Test::hello;// 可调用对象// 类的成员指针(变量)using ptr1 = int Test::*;// 属于Test类中的指针ptr1 pt = &Test::m_id;// 可调用对象Test ttt;(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表ttt.*pt = 100;cout << "m_id:" << ttt.m_id << '\n';return 0;
}

上述程序的输出结果为: 

C++11通过提供std::function 和 std::bind统一了可调用对象的各种操作。 

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

std::function必须要包含一个叫做functional的头文件,可调用对象包装器使用语法如下:

#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

 接下来演示可调用对象包装器的基本使用方法:

#include<iostream>
#include<functional>
using namespace std;
/*1.是一个函数指针2.是一个具有operator()成员函数的类对象(仿函数)3.是一个可被转换为函数指针的类对象4.是一个类成员函数指针或者类成员指针
*///普通函数
void print(int num, string name)
{cout << "id:" << num << ",name:" << name << '\n';
}using funcptr = void(*)(int, string);
//类
class Test
{
public:// 重载void operator()(string msg){cout << "仿函数:" << msg << '\n';}// 将类对象转化为函数指针operator funcptr()// 后面的这个()不需要写任何参数{// 不能返回hello,虽然hello的参数也是int和string,// 但是hello在未示例化之前是不存在的,world是属于类的return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址}void hello(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}static void world(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}int m_id = 520;string m_name = "luffy";
};int main()
{
#if 0Test t;t("我是要成为海贼王的男人");// 重载被执行Test tt;tt(19, "luffy");// 类的函数指针funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数// 给函数指针加上作用域就可以指向类中的非静态函数了using fptr = void(Test::*)(int, string);fptr f1 = &Test::hello;// 可调用对象// 类的成员指针(变量)using ptr1 = int Test::*;// 属于Test类中的指针ptr1 pt = &Test::m_id;// 可调用对象Test ttt;(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表ttt.*pt = 100;cout << "m_id:" << ttt.m_id << '\n';
#endif// 打包:// C++中的function主要用于包装可调用的实体,// 也就是函数。这些可调用的实体包括普通函数、函数指针、成员函数、静态函数、// lambda表达式和函数对象。所以,你可以认为std::function包装的都是函数。// 1.普通包装函数// 只是对print函数进行打包,f1是不会被调用的,若想f1中的函数体执行还需要额外的再次调用function<void(int, string)> f1 = print;// 2.包装类的静态函数function<void(int, string)> f2 = Test::world;// 3.包装仿函数Test ta;function<void(string)> f3 = ta;// 4.包装转化为函数指针的对象Test tb;function<void(int, string)> f4 = tb;// 调用:f1(1, "ace");f2(2, "sabo");f3("luffy");f4(3, "robin");return 0;
}

代码运行结果: 

 

通过测试代码可以得到结论:std::function可以将可调用对象进行包装,得到一个统一的格式,包装完成得到的对象相当于一个函数指针,和函数指针的使用方式相同,通过包装器对象就可以完成对包装的函数的调用了。

function作为回调函数使用:

 因为回调函数本身就是通过函数指针实现的,使用对象包装器可以取代函数指针的作用。

回调函数的基本概念和作用:

在C++中,回调函数(Callback Function)是指一种通过函数指针或函数对象传递给其他函数的函数。这种机制允许你在某个事件发生或条件满足时,通过调用指定的函数来实现定制的操作。

回调函数通常用于实现异步操作、事件处理、以及在框架或库中注册自定义行为。

回调函数的示例代码:

#include <iostream>// 定义回调函数的原型
typedef void (*CallbackFunction)(int);// 接受回调函数作为参数的函数
void performOperation(int data, CallbackFunction callback) {// 执行某些操作std::cout << "Performing operation with data: " << data << std::endl;// 调用回调函数callback(data);
}// 示例回调函数
void callbackFunction(int data) {std::cout << "Callback function called with data: " << data << std::endl;
}int main() {// 使用回调函数调用 performOperationperformOperation(42, callbackFunction);return 0;
}

输出结果为:

Performing operation with data: 42
Callback function called with data: 42

解释一下输出结果:

1. `performOperation` 函数被调用,传递了参数 `42`,然后输出了一条包含该数据的信息。

2. 在 `performOperation` 函数内部,回调函数 `callbackFunction` 被调用,将参数 `42` 传递给它。

3. `callbackFunction` 函数被执行,输出了一条包含传递给它的数据的信息。

因此,整体输出结果包括了两行信息,一行是在执行 `performOperation` 时的信息,另一行是在执行回调函数 `callbackFunction` 时的信息。这演示了回调函数的基本概念,其中一个函数在特定事件或条件发生时调用另一个函数。

接下来是关于function作为回调函数的使用的代码:

#include<iostream>
#include<functional>
using namespace std;
/*1.是一个函数指针2.是一个具有operator()成员函数的类对象(仿函数)3.是一个可被转换为函数指针的类对象4.是一个类成员函数指针或者类成员指针
*///普通函数
void print(int num, string name)
{cout << "id:" << num << ",name:" << name << '\n';
}using funcptr = void(*)(int, string);
//类
class Test
{
public:// 重载void operator()(string msg){cout << "仿函数:" << msg << '\n';}// 将类对象转化为函数指针operator funcptr()// 后面的这个()不需要写任何参数{// 不能返回hello,虽然hello的参数也是int和string,// 但是hello在未示例化之前是不存在的,world是属于类的return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址}void hello(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}static void world(int a, string s){cout << "number:" << a << ",name:" << s << '\n';}int m_id = 520;string m_name = "luffy";
};class A
{
public:// 构造函数参数是一个包装器对象// 这就意味着可以给这个构造函数传递四种类型的可调用对象// 传进来的可调用对象并没有直接使用,而是存在callback中// 在实例化对象后,调用notify函数,相当于一个回调操作A(const function<void(int, string)>& f) : callback(f){}void notify(int id, string name){callback(id, name);// 调用通过构造函数得到函数指针}private:function<void(int, string)> callback;
};int main()
{
#if 0Test t;t("我是要成为海贼王的男人");// 重载被执行Test tt;tt(19, "luffy");// 类的函数指针funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数// 给函数指针加上作用域就可以指向类中的非静态函数了using fptr = void(Test::*)(int, string);fptr f1 = &Test::hello;// 可调用对象// 类的成员指针(变量)using ptr1 = int Test::*;// 属于Test类中的指针ptr1 pt = &Test::m_id;// 可调用对象Test ttt;(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表ttt.*pt = 100;cout << "m_id:" << ttt.m_id << '\n';
#endif// 打包:// C++中的function主要用于包装可调用的实体,// 也就是函数。这些可调用的实体包括普通函数、函数指针、成员函数、静态函数、// lambda表达式和函数对象。所以,你可以认为std::function包装的都是函数。// 1.普通包装函数// 只是对print函数进行打包,f1是不会被调用的,若想f1中的函数体执行还需要额外的再次调用function<void(int, string)> f1 = print;// 2.包装类的静态函数function<void(int, string)> f2 = Test::world;// 3.包装仿函数Test ta;function<void(string)> f3 = ta;// 4.包装转化为函数指针的对象Test tb;function<void(int, string)> f4 = tb;// 调用:f1(1, "ace");f2(2, "sabo");f3("luffy");f4(3, "robin");A aa(print);aa.notify(1, "ace");A ab(Test::world);ab.notify(2, "sabo");// 包装仿函数也可以传参,这里不能是因为参数类型不一致// 这里包装仿函数的参数为(int, string)A ac(tb);ac.notify(3, "luffy");return 0;
}

上述代码的运行结果为: 

通过上面的例子可以看出,使用对象包装器std::function可以非常方便的将仿函数转换为一个函数指针,通过进行函数指针的传递,在其他函数的合适的位置就可以调用这个包装好的仿函数了。

另外,使用std::function作为函数的传入参数,可以将定义方式不相同的可调用对象进行统一的传递,这样大大增加了程序的灵活性。

本文参考:可调用对象包装器、绑定器 | 爱编程的大丙 (subingwen.cn)

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

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

相关文章

giee 添加公匙 流程记录

一、安装 百度网盘CSDN4文件夹下&#xff0c;或者官网下载&#xff1a;https://git-scm.com/downloads 二、生成密钥 1.右击打开git bash 2.$ ssh-keygen -t rsa -C “个人邮箱地址”&#xff0c;按3个回车&#xff0c;密码为空。 3.在C:\Users{windows用户名}.ssh目录下得到…

onvif协议笔记

一、简介 ONVIF官网 ONVIF协议网络摄像机&#xff08;IPC&#xff09;客户端程序开发&#xff08;1&#xff09;&#xff1a;专栏开篇 onvif协议开发 二、gSOAP gsoap官网 1、下载和编译 下载地址 #! /bin/sh # 指定源码目录(解压源码) GSOAP_SRCgsoap-2.8 PWDpwd echo &…

LiteClient工具箱:降低成本,减少监管风险

​​发表时间&#xff1a;2023年9月14日 BSV区块链协会的工程团队一直在为即将推出的LiteClient而努力工作&#xff0c;这是一套模块化的组件&#xff0c;可使简易支付验证&#xff08;SPV&#xff09;变得更加便利。 借助LiteClient工具箱&#xff0c;交易所可以通过区块头中…

网络空间搜索引擎- FOFA的使用技巧总结

简介 FOFA是一款网络空间测绘的搜索引擎&#xff0c;旨在帮助用户以搜索的方式查找公网上的互联网资产。 FOFA的查询方式类似于谷歌或百度&#xff0c;用户可以输入关键词来匹配包含该关键词的数据。不同的是&#xff0c;这些数据不仅包括像谷歌或百度一样的网页&#xff0c;还…

AWS RDS慢日志文件另存到ES并且每天发送邮件统计慢日志

1.背景&#xff1a;需要对aws rds慢日志文件归档到es&#xff0c;让开发能够随时查看。 2.需求&#xff1a;并且每天把最新的慢日志&#xff0c;过滤最慢的5条sql 发送给各个产品线的开发负责人。 3.准备&#xff1a; aws ak/sk &#xff0c;如果rds 在不同区域需要认证不同的…

七轴开源协作机械臂myArm视觉跟踪技术!

引言 ArUco标记是一种基于二维码的标记&#xff0c;可以被用于高效的场景识别和位置跟踪。这些标记的简单性和高效性使其成为机器视觉领域的理想选择&#xff0c;特别是在需要实时和高精度跟踪的场景中。结合机器学习和先进的图像处理技术&#xff0c;使用ArUco标记的机械臂系统…

「数据结构」二叉树1

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;C启航 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;树&#x1f349;二叉树&#x1f34c;特殊二叉树&#x1f34c;二叉树的性质&#x1f34c;存储结构 &#x1f349;…

Linux-----21、挂载

# 挂载命令 将硬件资源&#xff0c;或文件资源&#x1f4bf;&#xff0c;和&#x1f4c2;空目录&#x1f517;连接起来的过程 # mount linux 所有存储设备都必须挂载使用&#xff0c;包括硬盘 ​ 命令名称&#xff1a;mount ​ 命令所在路径&#xff1a;/bin/mount ​ 执行…

JavaWeb编程语言—登录校验

一、前言&简介 前言&#xff1a;小编的上一篇文章“JavaWeb编程语言—登录功能实现”&#xff0c;介绍了如何通过Java代码实现通过接收前端传来的账号、密码信息来登录后端服务器&#xff0c;但是没有实现登录校验功能&#xff0c;这代表着用户不需要登录也能直接访问服务器…

Qt-QTransform介绍与使用

QTransform是一个用于二维坐标系转换的类。我们知道Qt的坐标系是左上角为原点&#xff0c;x轴向右&#xff0c;y轴向下&#xff0c;屏幕上每个像素代表一个单位&#xff0c;那么&#xff0c;如果我们想要在屏幕上建立自己的坐标系用于绘制&#xff0c;就需要借助QTransform。 …

11.1 Linux 设备树

一、什么是设备树&#xff1f; 设备树(Device Tree)&#xff0c;描述设备树的文件叫做 DTS(DeviceTree Source)&#xff0c;这个 DTS 文件采用树形结构描述板级设备&#xff0c;也就是开发板上的设备信息&#xff1a; 树的主干就是系统总线&#xff0c; IIC 控制器、 GPIO 控制…

python图像二值化处理

目录 1、双峰法 2、P参数法 3、迭代法 4、OTSU法 图像的二值化处理是将图像上的像素点的灰度值设置为0或255&#xff0c;也就是将整个图像呈现出明显的只有黑和白的视觉效果。二值化是图像分割的一种最简单的方法&#xff0c;可以把灰度图像转换成二值图像。具体实现是将大…