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 0
	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';
#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() {
    // 使用回调函数调用 performOperation
    performOperation(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 0
	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';
#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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Linux] LVS负载均衡群集——DR模式

一、 DR模式的特点 直接路由&#xff1a; 在LVS_DR模式下&#xff0c;负载均衡器不修改数据包的IP地址&#xff0c;只修改目的MAC地址。这使得数据包可以直接路由到后端实际服务器上&#xff0c;而不需要返回到负载均衡器。 高性能&#xff1a; 由于数据包在传输过程中不需要回…

生物识别规划人脸识别方案的概述和特点

方案概述 人脸识别方案采用高性能AI芯片&#xff0c;支持RGB和IR摄像头&#xff0c; 支持LCD显示屏。 方案特点 • 普通RGB摄像头和IR摄像头同时参与3D成像RGB摄像头 支持屏幕回显 • 双目摄像头得到特征点视差计算人脸相 对3D深度信息&#xff0c; 同时利用可见光和红外 光…

【数据结构】树状数组算法总结

知识概览 树状数组有两个作用&#xff1a; 快速求前缀和 时间复杂度O(log(n))修改某一个数 时间复杂度O(log(n)) 例题展示 1. 单点修改&#xff0c;区间查询 题目链接 活动 - AcWing本活动组织刷《算法竞赛进阶指南》&#xff0c;系统学习各种编程算法。主要面向…

[极客大挑战 2019]Havefun1

1.别的博主写的非常好&#xff0c;我就不重复造轮子了 一位优秀师傅写的&#xff1a;https://blog.csdn.net/HackerQY/article/details/128503805

音频格式如何转为mp3?

音频格式如何转为mp3&#xff1f;各种各样的音频是在现代生活中肯定会接触到的&#xff0c;音频不仅能够让我们娱乐&#xff0c;也可以在办公和学习中使用&#xff0c;而且音频的格式非常多种多样&#xff0c;但不同的格式有不同的优缺点&#xff0c;例如兼容性问题&#xff0c…

沙盘模型3D打印加工服务建筑设计模型3D打印展览展示模型3D打印-CASAIM

随着3D打印技术的不断发展&#xff0c;沙盘模型3D打印已经成为建筑行业中的一项创新应用。这种技术能够将设计师的创意以实体形式呈现&#xff0c;为建筑项目的沟通和展示提供了更加直观和便捷的方式。本文将介绍CASAIM沙盘模型3D打印的优势和应用。 一、CASAIM沙盘模型3D打印的…

【MATLAB源码-第101期】基于matlab的蝙蝠优化算BA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 蝙蝠算法&#xff08;BA&#xff09;是一种基于群体智能的优化算法&#xff0c;灵感来源于蝙蝠捕食时的回声定位行为。这种算法模拟蝙蝠使用回声定位来探测猎物、避开障碍物的能力。在蝙蝠算法中&#xff0c;每只虚拟蝙蝠代表…

解决Visual Studio 各版本都出现新建项目后解决方案下没有文件和项目问题

一步一步创建C#控制台应用程序也会出错&#xff0c;这个你可能不会相信&#xff0c;我就遇到了这么一次&#xff0c;就在刚刚&#xff0c;是的&#xff0c;我都不敢相信&#xff0c;用了这么多年的新建一个控制台程序居然不正常了。新建完毕发现里面什么都没有&#xff0c;除了…

代码生成器底层原理:模板框架freemarker

1.按照设置好的模板文件就能生成Java&#xff0c;vue文件&#xff0c;前后端都可生成。 2.也可以进行复杂Excel到处&#xff1a;可以转成xml&#xff0c;用xml来制作模板&#xff0c;在生成excel 3.需要批量生成格式固定的一类文件的需求也可以使用模板框架freemarker 首先引…

四十四、Redis的数据持久化(RDB、AOF)

目录 一、定义 二、RDB 1、默认方案&#xff1a; 2、bgsave方案&#xff1a; 3、bgsave的基本流程&#xff1a; 4、RDB会在什么时候执行&#xff1f;save 60 1000代表什么含义&#xff1f; 5、RDB的缺点&#xff1a; 三、AOF 1、定义&#xff1a; 2、流程&#xff1a;…

【深度学习】Sentence Embedding-BERT-Whitening

前言 flow模型本身很弱&#xff0c;BERT-flow里边使用的flow模型更弱&#xff0c;所以flow模型不大可能在BERT-flow中发挥至关重要的作用。反过来想&#xff0c;那就是也许我们可以找到更简单直接的方法达到BERT-flow的效果。 BERT-whitening则认为&#xff0c;flow模型中涉及到…

ChatGPT引领AI时代:程序员、项目经理、产品经理、架构师、Python量化交易师的翅膀

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在当今AI时代&#xff0c;ChatGPT作为一项卓越…

三本光电从颓废到武汉年薪30w的本科经历经验与浅谈(毕业工作一年的嵌入式软件工程师经验分享)

三本光电从颓废到武汉年薪30w的本科经历经验与浅谈&#xff08;毕业工作一年的嵌入式软件工程师经验分享&#xff09; 文章目录 目前情况颓废时期项目时期第一次写单片机代码第一次接触计算机视觉第一次接触Linux驱动开发第一次接触FPGA和Verilog HDL第一次开发STM32创新实践及…

将yolo格式转化为voc格式:txt转xml(亲测有效)

1.文件目录如下所示&#xff1a; 对以上目录的解释&#xff1a; 1.dataset下面的image文件夹&#xff1a;里面装的是数据集的原图片 2.dataset下面的label文件夹&#xff1a;里面装的是图片对应得yolo格式标签 3.dataset下面的Annotations文件夹&#xff1a;这是一个空文件夹&…

自动驾驶技术入门平台分享:百度Apollo开放平台9.0全方位升级

目录 平台全方位的升级 全新的架构 工具服务 应用软件&#xff08;场景应用&#xff09; 软件核心 硬件设备 更强的算法能力 9.0版本算法升级总结 更易用的工程框架 Apollo开放平台9.0版本的技术升级为开发者提供了许多显著的好处&#xff0c;特别是对于深度开发需求…

Pycharm中如何使用Markdown?只需装这个插件!

一、前言 由于Markdown的轻量化、易读易写特性&#xff0c;并且对于图片&#xff0c;图表、数学式都有支持&#xff0c;许多网站都广泛使用Markdown来撰写帮助文档或是用于论坛上发表消息。 如GitHub、Reddit、Diaspora、Stack Exchange、OpenStreetMap 、SourceForge、简书等…

什么是低代码(Low-code) 低代码开发平台特点-优势介绍

随着数字化转型的加速&#xff0c;越来越多的企业开始认识到应用开发的重要性。然而&#xff0c;传统的应用开发方式往往需要耗费大量的时间和资源&#xff0c;而且开发周期长&#xff0c;难以满足企业的快速需求。在这样的背景下&#xff0c;Low Code Platform(低代码平台)应运…

windows10 固定电脑IP地址操作说明

windows10 固定电脑IP地址操作说明 一、无线网络的IP地址设置方法二、有线网络的IP地址设置方法 本文主要介绍&#xff0c;windows10操作系统下&#xff0c;不同的网络类型&#xff0c;对应的电脑IP地址设置方法。 一、无线网络的IP地址设置方法 在桌面右下角&#xff0c;点击…

OpenHarmony鸿蒙原生应用开发,ArkTS、ArkUI学习踩坑学习笔记,持续更新中。

一、AMD处理器win10系统下&#xff0c;DevEco Studio模拟器启动失败解决办法。 结论&#xff1a;在BIOS里面将Hyper-V打开&#xff0c;DevEco Studio模拟器可以成功启动。 二、ArkTS自定义组件导出、引用实现。 如果在另外的文件中引用组件&#xff0c;需要使用export关键字导…

【JavaSE】Java入门九(异常详解)

目录 异常 1.Java中异常的体系结构 2.异常的处理 3.自定义异常类 异常 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为异常&#xff0c;C语言中没有这个概念&#xff0c;接下来我们重点需要掌握异常处理体系&#xff08;try, catch, throw, finally&#xff…