C++中的回调函数再次总结(std::bind和function)

news2024/11/6 3:06:02

文章目录

  • 0 引入
  • 1、回调函数
    • 1.定义
    • 2.基本格式
  • 2、应用场景
    • 1.一件事需要多个独立步骤完成
    • 2.回调
  • 3、C++11中的std::function和bind
  • 4、引用


在这里插入图片描述

0 引入

最近看到一些文章说回调函数,再加上看到一些比较好的文章和代码,对于回调函数又有了重新的认识,在这里做了一点总结
之前转载过一篇文章:MFC中(TCP/IP)回调函数简析


1、回调函数

1.定义

1、回调函数来源于callback,意思就是说回电,试想是说我打电话给某人,某人没有接到但是留下未接电话,看到未接电话之后回过来电话,其实这个业务场景正是回调函数的精髓,下面会说到。
2、回调函数:字面意思是一个函数,更具体来说是一个函数指针,我们可以说使用函数指针的地方就是运用回调函数

2.基本格式

上代码:

#include <stdio.h>
typedef int(*callback)(int,int);        //定义一个函数指针

int add1(int a,int b)                   //定义一个函数
{
    return a+b;
 }
int add2(int a,int b,callback p)        //使用函数指针作为形参
{
    return (*p)(a,b);
}
int main(int argc,char *args[]){
    int res = add2(4,2,add1);          //使用函数
    printf("%d\n",res);
    return 0;
}

在这里 int res = add2(4,2,add1)使用就是运用了回调函数,总结:
1、定义函数指针的形式,如函数的形参和返回值

typedef int(*callback)(int,int)

2、在被调用者中实现该函数的定义

int add1(int a,int b) {return a+b;}

3、调用者传入函数指针

int add2(int a,int b,callback p)        
{
    return (*p)(a,b);
}

调用者一般用一个函数去接收指针,然后再调用者就可以使用该函数。上面例子在一个cpp中实现不是太好说,请看下面应用场景


2、应用场景

1.一件事需要多个独立步骤完成

举例在嵌入式中
代码如下(示例):

int callback1(int a) { return a;}                  //定义一个函数表示步骤1
int callback2(int a) { return a;}                  //定义一个函数表示步骤2
int callback3(int a) { return a;}                  //定义一个函数表示步骤3
int callback4(int a) { return a;}                  //定义一个函数表示步骤4
int callback5(int a) { return a;}                  //定义一个函数表示步骤5
int handle(int a, int(CallBack*)(int))
{
	CallBack(a);
}
int main(){
    handle(1,callback1);  
    handle(2,callback2); 
    handle(3,callback3); 
    handle(4,callback4); 
    handle(5,callback5);      
}

进一步的也可以用结构去封装一个步骤,步骤里面用函数指针去完成某件事情,然后在定义一个结构体数组,根据当前某个标识去查询数组,然后去执行某件事,这样整个框架就形成。

上述这些功能不这么用可以吗?当然可以,不这么用仅从功能来说完全写成函数就可以实现,在这里只是说有这么形式可以让工程更好理解。

2.回调

这个场景我觉得是用回调函数最为正确的时候:首先有两方,比如A与B,A去调用B某个函数B1,然后B又去使用A中函数A1,比如A调用B中处理数据的函数,然后B处理完成去通知。一般都是异步回调。
在实际工程应用中,比较典型的例子就是相机获取图像,一般买大华或者海康相机都会有SDK支持二次开发(借鉴于此,实际上我们自己开发sdk在穿大量数据的时候其实也是可以这么做),如下图(例子是从下文章来的):
c++11 回调函数(以相机SDK采集图像的方式进行讲解)
在这里插入图片描述

代码如下(示例):

#include <iostream>
#include <functional>
using namespace std;

/*回调函数原型声明*/
typedef function<void(int)> CALLBACK;

/*相机SDK底层A类*/
class A_Camera
{
public:
	void regeditCallBack(CALLBACK fun)/*注册回调函数*/
	{
		_fun = fun;
	}

	void getFrame()/*内部获取图像函数(B类调用者不需要关心它什么时候会执行)*/
	{
		/*采集到一帧数据_frame*/
		/****内部操作***/
		/***内部操作***/

		_frame = rand() % 10;
		_fun(_frame);/*回传给B_My类*/
	}

private:
	int _frame;
	CALLBACK _fun;
};

/*应用层B类*/
class B_My
{
public:
	void callBackFun(int frame)/*获取到A类的图像,此时frame就是一帧数据*/
	{
		cout << "B类获取到一帧数据:" << frame << endl;
	}
};

int main(int argc, char **argv)
{
	/*声明应用层B类对象*/
	B_My B; 

	auto Fun = bind(&B_My::callBackFun, B, placeholders::_1);/*中转一下,利用C++11特性*/

	/*声明底层相机A类*/
	A_Camera camera;
	camera.regeditCallBack(Fun);/*把B类的方法注册给A类*/

	/*以下只是模拟A类内部触发获取到图片,一共模拟触发10次*/
	for (int i = 0; i < 10; ++i)
	{
		camera.getFrame();
	}

	return 0;
}


3、C++11中的std::function和bind

在早期C样式编程当中,回调函数必须依赖函数指针来实现。
现在的C++11语言当中,又引入了 std::function 与 std::bind 来配合进行回调函数实现
回调的对象类型更是丰富,C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。
标准库中有大量函数应用到了回调函数,其中 std::sort 就是一个经典例子。
std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

# include <iostream>
# include <functional>
typedef std::function<int(int, int)> comfun;
// 普通函数
int add(int a, int b) { return a + b; }
// lambda表达式
auto mod = [](int a, int b){ return a % b; };
// 函数对象类
struct divide{
    int operator()(int denominator, int divisor){
        return denominator/divisor;
    }
};

int main(){
	comfun a = add;
	comfun b = mod;
	comfun c = divide();
    std::cout << a(5, 3) << std::endl;
    std::cout << b(5, 3) << std::endl;
    std::cout << c(5, 3) << std::endl;
}

std::bind可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表,理解下面的例子即可。

#include <iostream>
#include <functional>

class A {
public:
    void fun_3(int k,int m) {
        std::cout << "print: k = "<< k << ", m = " << m << std::endl;
    }
};

void fun_1(int x,int y,int z) {
    std::cout << "print: x = " << x << ", y = " << y << ", z = " << z << std::endl;
}

void fun_2(int &a,int &b) {
    ++a;
    ++b;
    std::cout << "print: a = " << a << ", b = " << b << std::endl;
}

int main(int argc, char * argv[]) {
    //f1的类型为 function<void(int, int, int)>
    auto f1 = std::bind(fun_1, 1, 2, 3); 					//表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
    f1(); 													//print: x=1,y=2,z=3

    auto f2 = std::bind(fun_1, std::placeholders::_1, std::placeholders::_2, 3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定
    f2(1, 2);												//print: x=1,y=2,z=3
 
    auto f3 = std::bind(fun_1, std::placeholders::_2, std::placeholders::_1, 3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定
    //注意: f2  和  f3 的区别。
    f3(1, 2);												//print: x=2,y=1,z=3

    int m = 2;
    int n = 3;
    auto f4 = std::bind(fun_2, std::placeholders::_1, n); //表示绑定fun_2的第一个参数为n, fun_2的第二个参数由调用f4的第一个参数(_1)指定。
    f4(m); 													//print: a=3,b=4
    std::cout << "m = " << m << std::endl;					//m=3  说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
    std::cout << "n = " << n << std::endl;					//n=3  说明:bind对于预先绑定的函数参数是通过值传递的,如n
    
    A a;
    //f5的类型为 function<void(int, int)>
    auto f5 = std::bind(&A::fun_3, &a, std::placeholders::_1, std::placeholders::_2); //使用auto关键字
    f5(10, 20);												//调用a.fun_3(10,20),print: k=10,m=20

    std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
    fc(10, 20);   											//调用a.fun_3(10,20) print: k=10,m=20 

    return 0; 
}

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
1、将可调用对象和其参数绑定成一个仿函数;
2、只绑定部分参数,减少可调用对象传入的参数。


4、引用

1、【C++】C++11的std::function和std::bind用法详解
2、回调函数的使用


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

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

相关文章

react源码分析:babel如何解析jsx

同作为MVVM框架&#xff0c;React相比于Vue来讲&#xff0c;上手更需要JavaScript功底深厚一些&#xff0c;本系列将阅读React相关源码&#xff0c;从jsx -> VDom -> RDOM等一些列的过程&#xff0c;将会在本系列中一一讲解 工欲善其事&#xff0c;必先利其器 经过多年的…

性能测试 之cpu 线程 上下文切换问题分析

使用 stress-ng: 性能测试模拟线程上下文切换 上篇文章使用了stress-ng 模拟了 进程上下文切换导致的性能问题&#xff0c; 现在我们在使用 该工具模拟线程上下文切换&#xff0c;那么进程和线程有什么区别呢 抽象&#xff1a;线程&#xff08;thread&#xff09;是操作系统能…

MEMM最大熵模型

最大熵模型&#xff08;MEMM&#xff09;: 提出背景&#xff1a;解决模型三个缺点 最大熵结构&#xff1a;HMM框架加上多项的逻辑回归。 HMM缺点&#xff1a; 1.观测独立假设和齐次马尔可夫假设 解决办法&#xff1a;调转模型箭头 2.模型建模和求解不一致&#xff08;建模&am…

农产品溯源中GIS应用

农产品溯源中GIS应用 摘要 构建“从田间地头到餐桌”的农产品安全生产与溯源体系需求日益迫切。农产品的食品安全也是维持人们的生命健康重要因素之一。当前&#xff0c;农业信息化推进速度非常的迅速&#xff0c;各类型农业相关基础设施正在不断加强&#xff0c;信息技术能提…

vs2013的使用及编译中遇到的问题

目录 一、vs2013的使用 1、新建项目 2、新建源文件 3、编辑代码 但是如果每次新建文件都要加上这一句就很麻烦&#xff0c;所以这里提供一个一劳永逸的方法 二、运行代码的中的小问题 问题1&#xff1a;scanf函数不安全 解决办法 法1.用报错提示中的 scanf_s 来代替 scanf…

ShaderGraph实现序列帧动画

介绍 上篇我们介绍了ShaderLab编程实现序列帧动画,这里我们介绍一下如何使用可视化界面ShaderGraph来实现。 在使用ShaderGraph的过程中,我们可以了解ShaderGranph的一些操作,由于上篇文章已经分享了原理方面的知识,这里不再赘述。我们便开始ShaderGraph来实现序列帧动画。…

Proxyless Mesh 在 Dubbo 中的实践

作者&#xff1a;王程铭 背景 随着 Dubbo 3.1 的 release&#xff0c;Dubbo 在云原生的路上又迈出了重要的一步。在这个版本中添加了 Proxyless Mesh 的新特性&#xff0c;Dubbo Proxyless Mesh 直接实现 xDS 协议解析&#xff0c;实现 Dubbo 与 Control Plane 的直接通信&am…

深度学习训练营实现minist手写数字识别

深度学习训练营原文链接环境介绍前置工作设置GPU导入要使用的包进行归一化操作样本可视化调整图片格式构建CNN网络编译模型模型训练预测操作原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训…

OpenCV图像处理——卷积操作

总目录 图像处理总目录←点击这里 二十五、卷积操作 25.1、预处理 # 指定输入图像 ap argparse.ArgumentParser() ap.add_argument("-i", "--image", requiredTrue, help"path to the input image") args vars(ap.parse_args())# 分别构建…

【数据结构趣味多】顺序表基本操作实现(Java)

目录 顺序表 1.定义顺序顺序表 2.顺序表功能 3.函数实现&#xff08;java实现&#xff09;&#xff1f; 打印顺序表display()函数 新增元素函数add() (默认在数组最后新增) 在 pos 位置新增元素add()函数&#xff08;与上方函数构成重载&#xff09; 判定是否包含某个元素…

XctNet:从单个X射线图像重建体积图像的网络

摘要 传统的计算机断层扫描&#xff08;CT&#xff09;通过使用不同角度的X射线投影计算逆氡变换来生成体积图像&#xff0c;这导致高剂量辐射、长重建时间和伪影。生物学上&#xff0c;可以利用先前的知识或经验在一定程度上从2D图像中识别体积信息。提出了一种深度学习网络Xc…

为什么要使用 kafka,为什么要使用消息队列?

总结以下两点&#xff1a; 1、缓冲和削峰&#xff1a; 上游数据时有突发流量&#xff0c;下游可能扛不住&#xff0c;或者下游没有⾜够多的机器来保证冗余&#xff0c;kafka在中间可以起到⼀个缓冲的作⽤&#xff0c;把消息暂存在kafka中&#xff0c;下游服务就可以按照⾃⼰的节…

B. Moderate Modular Mode(nmodx=ymodn )

Problem - 1603B - Codeforces 帮助他找到一个整数n&#xff0c;使得1≤n≤2⋅1018&#xff0c;并且nmodxymodn。这里&#xff0c;amodb表示a除以b后的余数。如果有多个这样的整数&#xff0c;请输出任何一个。可以证明&#xff0c;在给定的约束条件下&#xff0c;这样的整数总…

图的关键路径(含多支交叉路径分离输出)

文章目录关键路径的理解关键路径求解的图解与分析关键路径查找的代码实现多支交叉路径的分离输出总结此文代码均可在Windows与Linux操作系统下的常用编译器上运行&#xff0c;例如&#xff1a;vs、vscode、Dev-C等等。关键路径的理解 图的关键路径一般是在求从一个顶点到另一个…

RocketMQ-RocketMQ部署(Linux、docker)

文章目录一、Linux1、单机部署RocketMQ> 前置条件第一步、官网下载 并 上传至服务器第二步、配置jdk环境第三步、修改初始内存第四步、启动 NameServer第五步、启动 Broker第六步、关闭RocketMQDemo&#xff1a;发送与接收消息测试 (Linux端)2、部署可视化管理工具—rocketm…

tictoc 例子理解 13-15

tictoc13-tictoc13 子类化cMessage生成消息&#xff0c;随机目标地址tictoc 14 在13的基础上增加两变量显示于仿真界面tictoc 15 模型数据输出为直方图tictoc13 子类化cMessage生成消息&#xff0c;随机目标地址 在这一步中&#xff0c;目标地址不再是节点2——我们绘制了一个…

[附源码]计算机毕业设计springboot现代诗歌交流平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MySQL存储过程

目录 存储过程 1、存储过程的概念 2、存储过程的优点 3、创建存储过程 格式&#xff1a; 4、调用存储过程 格式 5、查看存储过程 格式&#xff1a; 6、存储过程的参数 7、删除存储过程 格式&#xff1a; 8、存储过程的控制语句 准备a表 &#xff08;1&#xff09;条…

Spring基础篇:注入

第一章&#xff1a;注入 一&#xff1a;什么是注入 &#xff08;Injection&#xff09;注入就是通过Spring的工厂类和spring的配置文件&#xff0c;对spring所创建的对象进行赋值&#xff0c;为成员变量进行赋值 二&#xff1a;为什么注入 为什么需要Spring工厂创建对象的时…

[附源码]Python计算机毕业设计SSM开放式在线课程教学与辅助平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…