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.mzph.cn/news/236832.shtml

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

相关文章

LeetCode刷题--- 括号生成

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 http://t.csdnimg.cn/6AbpV 数据结构与算法 http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述递归递归、搜…

Nginx 实战闲谈第一讲:HTTP协议介绍

基本介绍 1.HTTP含义 HTTP 全称&#xff1a;Hyper Text Transfer Protocol 中文名&#xff1a;超文本传输协议 HTTP就是将用户的请求发送到服务器&#xff0c;将服务器请求到的内容传输回给浏览器&#xff0c;浏览器进行解析&#xff0c;解析后变成便于观看的页面。&#x…

vscode配置node.js调试环境

node.js基于VSCode的开发环境的搭建非常简单。 说明&#xff1a;本文的前置条件是已安装好node.js(具体安装不再赘述&#xff0c;如有需要可评论区留言)。 阅读本文可掌握&#xff1a; 方便地进行js单步调试&#xff1b;方便地查看内置的对象或属性&#xff1b; 安装插件 C…

java:获取线程的Id

JDK 19及以后版本&#xff0c;使用Thread的threadId()方法获取当前线程的Id&#xff08;identifier &#xff09;。这个Id是一个正的长整型&#xff0c;在线程创建的时候产生。这个Id是唯一的&#xff0c;并且在线程的生命周期期间保持不变。 JDK 19以前的版本&#xff0c;使用…

Unity中Shader平移矩阵

文章目录 前言方式一&#xff1a;对顶点本地空间下的坐标进行相加平移1、在属性面板定义一个四维变量记录在 xyz 上平移多少。2、在常量缓冲区进行申明3、在顶点着色器中&#xff0c;在进行其他坐标转化之前&#xff0c;对模型顶点本地空间下的坐标进行转化4、我们来看看效果 方…

Java:将字符串重复多次串接起来输出

String的函数public String repeat(int count)&#xff0c;可以将当前字符串重复count次串接起来输出。 如果字符串是空的&#xff0c;或者count的值是0&#xff0c;那么返回空字符串。 代码示例1&#xff1a; package com.thb;public class Test5 {public static void main(…

CVE-2022-22978 Spring Security越权访问漏洞

简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 Spring Security底层实现为一条过滤器链&#xff0c;就是用户请求进来&#xff0c;判断有没有请求的权限&#xff0c;抛出异常&#xff0c;重定向跳转。 影响版本 S…

【教程】cocos2dx资源加密混淆方案详解

1,加密,采用blowfish或其他 2,自定是32个字符的混淆code 3,对文件做blowfish加密,入口文件加密前将混淆code按约定格式(自定义的文件头或文件尾部)写入到文件 4,遍历资源目录,对每个文件做md5混淆,混淆原始串“相对路径”“文件名”混淆code, 文件改名并且移动到资源目录根…

C#线程的定义和使用方法

引言 在C#编程语言中&#xff0c;线程是一种并发执行的机制&#xff0c;允许程序同时执行多个任务。线程的使用使得我们能够利用计算机的多核处理器&#xff0c;实现程序的并行执行&#xff0c;提高系统的性能和响应能力。本文将详细介绍C#中线程的定义和使用方法&#xff0c;涵…

大数据可视化BI分析工具Apache Superset结合内网穿透实现远程访问

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…

Flask ImportError: DLL load failed: 找不到指定的模块。

一、anaconda环境 将anaconda3安装路径下DDL目录中的 libcrypto-1_1-x64.dll 和 libssl-1_1-x64.dll 拷贝到 虚拟环境目录下的DLL中 完美解决 成功了给个赞吧&#xff01;

三大主流前端框架介绍及选型

在前端项目中&#xff0c;可以借助某些框架&#xff08;如React、Vue、Angular等&#xff09;来实现组件化开发&#xff0c;使代码更容易复用。此时&#xff0c;一个网页不再是由一个个独立的HTML、CSS和JavaScript文件组成&#xff0c;而是按照组件的思想将网页划分成一个个组…

智能优化算法应用:基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.未来搜索算法4.实验参数设定5.算法结果6.…

Java:获取线程组的最大优先级

java.lang.ThreadGroup的getMaxPriority()函数返回该线程组的最大优先级。这个最大优先级就等于该线程组中新创建线程的最大优先级。 代码示例&#xff1a; package com.thb;public class Test5 {public static void main(String[] args) {ThreadGroup threadGroup Thread.c…

Unity | Shader基础知识(第八集:案例<漫反射材质球>)

目录 一、本节介绍 1 上集回顾 2 本节介绍 二、什么是漫反射材质球 三、 漫反射进化史 1 三种算法结果的区别 2 具体算法 2.1 兰伯特逐顶点算法 a.本小节使用的unity自带结构体。 b.兰伯特逐顶点算法公式 c.代码实现——兰伯特逐顶点算法 2.2 代码实现——兰伯特逐…

【C++】初识模板

本文目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 3. 类模板3.1 类模板的定义格式3.2 类模板的实例化 1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int&…

【控制器局域网】CAN报文学习笔记(四)之 字节排序、信号提取实例1

以下面的表格来表示字节顺序和位顺序&#xff0c;用红色表示高位MSB&#xff0c;蓝色表示低位LSB&#xff0c;绿色为LSB到MSB的过度 Bit oderMSB→→→→→→LSBByte oder\Bit7Bit6Bit5Bit4Bit3Bit2Bit1Bit0MSBByte076543210↓Byte115141312111098↓Byte22322212019181716↓By…

【Proteus/8086】swjtu西南交大微机与接口技术实验:计时器

实验内容: 计时器基本功能: 1)CPU必须用8086 2)计时器最小计时单位为秒。 3)以00:00格式显示计时,前2位表示分钟,后2位表示秒。 4)计时器是正计时方式 5)有暂停、继续计时功能 6&#xff09;有复位计时功能 7&#xff09;每次按下暂停键&#xff0c;能显示计时间隔时间 参考…

Vue 2.5 入门学习记录

Vue 2.5 入门学习记录 1. 基础知识Vue 是什么Vue引入方式Vue特点Vue实例中的数据事件方法Vue中的属性绑定和双向绑定Vue中的v-if、v-show、v-fortoDoList制作局部组件&全局组件 2. vue-cli工程3. 工程案例实践使用vue-cli实现todoList及删除某个元素全局样式与局部样式 4. …

CentOS安装Python解释,CentOS设置python虚拟环境,linux设置python虚拟环境

一、安装python解释器 1、创建解释器安装的目录&#xff1a;/usr/local/python39 cd /usr/local mkdir python39 2、下载依赖 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make libffi-devel xz-devel …