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

news2024/11/26 5:29:21

在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)

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

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

相关文章

【MAC】M2 安装mysql

一、docker下载地址 下载地址 二、安装docker完成 三、安装mysql 一、拉取镜像 # 拉取镜像 docker pull mysql# 或者 docker pull mysql:latest# 以上两个命令是一致的&#xff0c;默认拉取的就是 latest 版本的# 我们还可以用下面的命令来查看可用版本&#xff1a; docker…

[德人合科技]——设计公司 \ 设计院图纸文件数据 | 资料透明加密防泄密软件

国内众多设计院都在推进信息化建设&#xff0c;特别是在异地办公、应用软件资产规模、三维设计技术推广应用以及协同办公等领域&#xff0c;这些加快了业务的发展&#xff0c;也带来了更多信息安全挑战&#xff0c;尤其是对于以知识成果为重要效益来源的设计院所&#xff0c;防…

Docker单点部署[8.11.3] Elasticsearch + Kibana + ik分词器

文章目录 一、Elasticsearch二、Kibana三、访问四、其他五、ik分词器第一种&#xff1a;在线安装第二种&#xff1a;离线安装 Elasticsearch 和 Kibana 版本一般需要保持一致才能一起使用&#xff0c;但是从 8.x.x开始&#xff0c;安全验证不断加强&#xff0c;甚至8.x.x之间…

【每日一题】—— C. Largest Subsequence(Codeforces Round 915 (Div. 2))(规律、字符串处理)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

制作一个多行时正确宽度的Textview,Android Textview 换行时宽度过长 右侧空白区域挤掉页面元素的解决方案

优化 Android 布局&#xff1a;创建自适应宽度的 TextView 引言 在Android应用开发中&#xff0c;布局优化是提升应用性能和用户体验的关键环节之一。特别是对于那些内容密集型的应用&#xff0c;如何高效地展示和管理文本内容成为了一个挑战。最近&#xff0c;在处理一个布局…

市场全局复盘 20231218

昨日回顾: SELECT CODE,成交额排名,净流入排名,代码,名称,DDE大单金额,涨幅,主力净额,DDE大单净量,CONVERT(DATETIME, 最后封板, 120) AS 最后封板,涨停分析,_3日涨幅百分比,连板天,封单额,封单额排名,DDE散户数量,总金额,获利盘 FROM dbo.全部A股20231218_ALL WHERE 连板天…

社交网络分析4(下):社交网络链路预测分析、LightGBM框架、LLSLP方法(LightGBM 堆叠链路预测)、堆叠泛化 、社交网络链路预测分析的挑战

社交网络分析4 写在最前面LightGBMLightGBM简介GBDT的核心概念和应用LightGBM的特点LightGBM与GBDT的比较 LightGBM的原理与技术GBDT的传统算法LightGBM的创新算法 GOSS&#xff08;Gradient-based One-Side Sampling&#xff09;算法解析概念和工作原理算法的逻辑基础GOSS算法…

PIC单片机项目(4)——基于PIC16F877A的温度光照检测装置

1.功能设计 基于PIC16F877A单片机&#xff0c;使用DS18B20进行温度测量&#xff0c;使用光敏电阻进行光照测量&#xff0c;将测量值实时显示在LCD1602屏幕上&#xff0c;同时可以设定光照阈值和温度阈值。当温度大于阈值&#xff0c;则蜂鸣器报警&#xff0c;当光照小于阈值&am…

EnvoyFilter API

目录 原文链接 https://onedayxyy.cn/docs/EnvoyFilter-API 本节实战 实战名称&#x1f6a9; 实战&#xff1a;EnvoyFilter API-全局范围-2023.12.18(测试成功)&#x1f6a9; 实战&#xff1a;EnvoyFilter API-配置优先级-2023.12.18(测试成功)&#x1f6a9; 实战&#xff1a…

开发企业展示小程序的关键步骤和技巧

随着移动互联网的快速发展&#xff0c;小程序已经成为企业展示形象、推广产品和服务的重要工具。拥有一个优秀的小程序可以帮助企业提高品牌知名度&#xff0c;吸引更多潜在客户&#xff0c;提升用户体验。以下是拥有一个展示小程序的步骤&#xff1a; 确定需求和目标 首先&am…

实时时钟(RTC)的选择与设计:内置晶体与外置晶体的优缺点对比

实时时钟(RTC)作为一种具备独立计时和事件记录功能的设备&#xff0c;现已广泛应用于许多电子产品中&#xff0c;并对时钟的精度要求越来越高。根据封装尺寸、接口方式、附加功能、时钟精度和待机功耗等因素进行分类&#xff0c;市场上有各种种类的RTC产品可供选择。 而在设计…

计网01 计算机网络基础

一、计算机网络基本概念 1、什么是计算机网络 网络&#xff1a;由两台或多台计算机通过网络设备串联&#xff08;网络设备通过传输介质串联&#xff09;而形成的网络网络设备&#xff1a;计算机、路由交换、防火墙、上网行为管理等传输介质&#xff1a;双绞线&#xff08;网线…

Unity中URP下的顶点偏移

文章目录 前言一、实现思路二、实现URP下的顶点偏移1、在顶点着色器中使用正弦函数&#xff0c;实现左右摇摆的效果2、在正弦函数的传入参数中&#xff0c;加入一个扰度值&#xff0c;实现不规则的顶点偏移3、修改正弦函数的振幅 A&#xff0c;让我们的偏移程度合适4、修改正弦…

使用特殊打字机键入单词的最少时间(贪心算法)

有一个特殊打字机&#xff0c;它由一个圆盘 和一个 指针组成&#xff0c; 圆盘上标有小写英文字母 a 到 z。只有 当指针指向某个字母时&#xff0c;它才能被键入。指针初始时指向字符 a 。 每一秒钟&#xff0c;你可以执行以下操作之一&#xff1a; 将指针顺时针或者逆时针移…

BearPi Std 板从入门到放弃 - 先天神魂篇(9)(RT-Thread DAC->ADC)

简介 RT-Thread DAC->ADC 使用, 就是DAC1输出模拟量, ADC1 读取模拟量转化成电压值, 基于开发板 &#xff1a; Bearpi Std(小熊派标准板)主芯片: STM32L431RCT6串口: Usart1DAC1: PA5, OUT2ADC1: PC2, IN3将板子上的E53 接口, 5 和 6用排线相连, 即实现内部DAC1->ADC1 …

TDDL笔记

TDDL分三层: Matrix层; 规则的管理 固定哈希算法&#xff0c;基本能保证数据均匀分布&#xff0c;它也是 TDDL 的默认路由算法。根据某个字段(如整形的 id 或者字符串的 hashcode)对分库的数量或者分表的数量进行取模&#xff0c;根据余数路由到对应的位置。一致性哈希算法&a…

【Docker】Docker安装部署maven私服

文章目录 镜像拉取构建nexus实例登录maven私服如何查看实例初始化的admin密码呢&#xff1f;1.查看容器挂载卷2.找到nexus_nexus_data查看挂载卷详情3.查看admin账号密码4.登录并重置密码 使用nexus私服1.设置settings.xml2.设置idea pom 出现的问题小插曲 镜像拉取 docker pu…

大数据Doris(三十八):Aggregate 和 Uniq 模型中的 ROLLUP

文章目录 Aggregate 和 Uniq 模型中的 ROLLUP 一、获得每个用户的总消费

Intel® Enclave Exiting Events(四)

文章目录 前言一、Compatible Switch to the Exiting Stack of AEX二、State Saving by AEX三、Synthetic State on Asynchronous Enclave Exit3.1 Processor Synthetic State on Asynchronous Enclave Exit3.2 Synthetic State for Extended Features3.3 Synthetic State for …

C# WPF上位机开发(usb设备访问)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前很多嵌入式设备都支持usb访问&#xff0c;特别是很多mcu都支持高速usb访问。和232、485下个比较&#xff0c;usb的访问速度和它们基本不在一个…