C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

news2024/11/26 23:42:57

本文主要介绍回调函数的使用,包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。

🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:C/C++精进之路
🎀CSDN主页 发狂的小花
🌄人生秘诀:学习的本质就是极致重复!

目录

1 回调函数

2 C语言回调函数

2.1 函数指针

2.1.1 函数指针变量

2.1.2 定义函数类型

2.1.3 定义函数指针类型

2.2 C语言回调函数实例

2.3 C语言异步编程

3 C++回调函数

3.1 C++简单的回调实现

3.2 简答的异步回调

3.3 静态成员函数实现异步回调(有线程)


1 回调函数

        回调函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。这种机制常用于事件处理、异步编程和处理各种操作系统和框架的API。

具体来说,一个函数可以将其它的函数(也就是回调函数)作为参数传入,当特定的事件触发或者任务完成时,这个被传入的函数就自动执行。这样的设计使得代码更加模块化,增强了代码的可读性和可重用性。

        回调函数是一种设计理念,与编程语言无关,C语言中可以通过函数指针实现回调函数

        回调函数主要用于异步编程和事件处理中。

        回调函数提供了一种强大的工具,允许我们将复杂的问题分解为更小的部分进行解决,同时也增加了代码的灵活性和扩展性。

2 C语言回调函数

        C语言的回调函数是使用函数指针实现的,函数作为参数传给另一个函数,传参的函数被称为回调函数。

2.1 函数指针

2.1.1 函数指针变量

        指向函数的指针变量称为函数指针。本质上是一个指针。

        定义方式:

        返回类型 (*指针变量名)(参数列表);

        其中,返回类型表示函数返回值的类型,参数列表表示函数接受的参数类型和个数。指针变量名就是函数指针的名称,它可以像普通变量一样被赋值和使用。

        使用实例:

  例如,定义一个指向返回值为int类型、接受两个int类型参数的函数的指针:

int (*pFunc)(int, int);

C++中可以使用new运算符动态分配内存来存储函数指针:

int (*pFunc)(int, int) = new int(*func)(int a, int b);

其中,func是一个已定义的函数,pFunc是指向该函数的指针。使用delete运算符释放内存:

delete pFunc;

2.1.2 定义函数类型

       定义方式:

        typedef 返回类型 新的函数名(参数列表);

        使用实例:

定义一个返回类型是int,参数为两个int数据,的函数类型

typedef int INT_func(int,int);

        调用方式:

int sub(int a, int b)
{
    return (a-b);
}

// add 为指针类型,函数的地址,因此需要声明变量为指针类型
INT_func *add_p = add;
int ret = add_p(12,13);

一个例子:

#include <stdio.h>

typedef int INT_func(int,int);


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

int sub(int a, int b)
{
    return (a-b);
}

int multi(int a,int b)
{
    return (a*b);
}


int main()
{

    INT_func *add_p = add;
    int ret = add_p(12,13);
    printf("12+13 = %d \n",ret);

    INT_func *sub_p = sub;
    int ret1 = sub_p(12,13);
    printf("12-13 = %d \n",ret1);

    INT_func *mutti_p = multi;
    int ret2 = mutti_p(12,23);
    printf("12*13 = %d \n",ret2);

    return 0;
}

        运行结果:

2.1.3 定义函数指针类型

         定义方式:

        typedef 返回类型 (*新的函数名)(参数列表);

        使用实例:

定义一个返回类型是int,参数为两个int数据,的函数类型

typedef int (*INT_func)(int,int);

        调用方式:

int sub(int a, int b)
{
    return (a-b);
}

// add 为指针类型,函数的地址,因此需要声明变量为指针类型
INT_func  add_p = add;
int ret = add_p(12,13);

一个例子:

#include <stdio.h>

typedef int (*INT_func)(int,int);


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

int sub(int a, int b)
{
    return (a-b);
}

int multi(int a,int b)
{
    return (a*b);
}


int main()
{

    INT_func add_p = add;
    int ret = add_p(12,13);
    printf("12+13 = %d \n",ret);

    INT_func sub_p = sub;
    int ret1 = sub_p(12,13);
    printf("12-13 = %d \n",ret1);

    INT_func mutti_p = multi;
    int ret2 = mutti_p(12,23);
    printf("12*13 = %d \n",ret2);

    return 0;
}

        运行结果:

2.2 C语言回调函数实例

        在C语言中,回调函数是一种常见的编程模式,它允许一个函数作为参数传递给另一个函数。这种特性使得我们可以将一些特定的任务委托给其他函数来完成。

#include <stdio.h>

// 定义一个函数指针类型
typedef void (*Callback)(int);

// 定义一个函数,接受一个回调函数作为参数
void do_something(Callback callback) {
    // 在这里做一些操作...
    printf("Doing something...\n");

    // 然后调用回调函数
    callback(42);
}

// 定义一个回调函数
void my_callback(int data) {
    printf("Callback called with data: %d\n", data);
}

int main() {
    // 将回调函数传递给do_something函数
    do_something(my_callback);

    return 0;
}

        在这个例子中,do_something函数接受一个Callback类型的参数,这是一个指向函数的指针。然后,do_something函数调用这个回调函数,并将一个整数作为参数传递给它。

my_callback函数是一个简单的回调函数,它接受一个整数参数并打印出来。

main函数中,我们将my_callback函数传递给do_something函数,这样当do_something函数完成其操作后,它会调用my_callback函数。

        运行结果:

2.3 C语言异步编程

        回调函数在异步编程中有着重要的作用,在接口编程应用广泛,实战中经常会注册一个回调函数来接收其他程序返回的数据,这样做的好处是调用者无需主动去获取被调用者的数据,仅仅需要回调,让被调用者去传送数据,这样就形成了回调,这是回调最常见的应用。

        假设A是视频流数据,每隔一段时间会将数据传送给B,B有两种接收方式:

        (1)A将视频数据存储好,提供接口,B根据自己的需求去调用,无需考虑时间,主动权在B。

        (2)A利用回调机制,实现回调函数,当数据来临时通知B来取数据,B注册回调函数接收数据,这样主动权在A。

        很显然第一种取数据的方式,由于B不知道数据何时来临比较低效,而第二种取数据方式A通知B来取数据,效率比较高,间接的实现了中断取数。

        这里就用到了回调,B注册一个函数,将函数地址传给A,A中调用该地址获取到B注册的函数时,我们就称这个函数为回调函数。

一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// A的实现,一般会隐藏
typedef void (*CallbackPtr)(int); //函数指针定义

typedef struct parameter{
    int a ;
    CallbackPtr callback;
}parameter; 

// 创建实例
parameter ParaData;

void* callback_thread(void *p1)//此处用的是一个线程
{
    //do something
    // 循环改变p->a的值为为0 2 3 4 5 6 7 8 9 ,每隔3秒改变一次
    parameter* p = (parameter*)p1 ;
    while(1)
    {
        sleep(3);//延时3秒执行callback函数
        p->callback(p->a);//函数指针执行函数,这个函数来自于应用层B
        p->a = (p->a + 1) % 10;
    }
}

void startup_app_A()
{
    // 初始化为0
    ParaData.a = 0;
    CallbackPtr init_call_back;
    ParaData.callback = init_call_back;
     //创建线程
    pthread_t thing1;
    pthread_create(&thing1,NULL,callback_thread,(void *) &ParaData);
}

// 给B的接口,接收注册函数
extern void SetCallBackFun(CallbackPtr callback)
{
    printf("SetCallBackFun print! \n");
    ParaData.callback = callback;
}


// //-----------------------应用者B-------------------------------
void fCallBack(int a)       // 应用者增加的函数,此函数会在A中被执行
{
    //do something
    printf("B得到A的数据 = %d\n",a);
}


int main(void)
{
    // 启动A
    startup_app_A();

    SetCallBackFun(fCallBack);
    
    // 主函数
    while (1)
    {
        // std::cout << "main function" << std::endl;
        printf("main function\n");
        sleep(2);
    }
    

    

    return 0;
}

        运行结果:

        分析结果,可以看出A提供的数据会回调B,B会每隔一定的时间收到数据,完成回调,对于一般的编程会将A的操作封装起来,只提供以下接口和函数指针类型:

typedef void (*CallbackPtr)(int);
extern void SetCallBackFun(CallbackPtr callback);

        这样B可以每次注册回调函数获取A传来的数据,如下:

void fCallBack(int a)       // 应用者增加的函数,此函数会在A中被执行
{
    //do something
    printf("B得到A的数据 = %d\n",a);
}

        主函数中调用:

SetCallBackFun(fCallBack);

        这样就完成了一个C语言回调的异步编程,这在驱动领域极其常见,比如摄像头输出50fps的UVC流,程序员用UVC来完成相应的任务,这样就可以使用异步回调的方式实现。

3 C++回调函数

        C++回调函数是一种将函数作为参数传递给另一个函数的技术。在C++中,回调函数通常使用函数指针或std::function对象来实现。

        函数回调类型的定义:

typedef std::function<void(int)> Callback;

        上述定义了一个返回值为void,参数为一个int类型参数的函数回调类型Callback,等同于以下的函数指针类型:

typedef void (*Callback)(int);

3.1 C++简单的回调实现

#include <iostream>
#include <functional>

// 定义一个回调函数类型
typedef std::function<void(int)> Callback;

// 定义一个接受回调函数的函数
void process(int value, Callback callback) {
    std::cout << "处理值: " << value << std::endl;
    callback(value); // 调用回调函数
}

// 定义一个回调函数
void myCallback(int result) {
    std::cout << "回调结果: " << result << std::endl;
}

int main() {
    int value = 42;
    process(value, myCallback); // 传递回调函数给process函数
    return 0;
}

        运行结果:

3.2 简答的异步回调

#include <iostream>
#include <functional>
#include <thread>
#include <chrono>
#include <unistd.h>
void async_operation(std::function<void(int)> callback) {
    // 模拟一些计算
    std::this_thread::sleep_for(std::chrono::seconds(1));
    int result = 42;
    // 调用回调函数并将结果传递给它
    callback(result);
}
void callback_function(int result) {
    std::cout << "Async operation completed with result: " << result << std::endl;
}
int main() {
    // 发起异步操作,并在操作完成后调用回调函数
    async_operation(callback_function);
    // 主线程继续执行其他任务
    while (1)
    {
       std::cout << "Main thread continues to do other work..." << std::endl;
       sleep(1);
    }
    
    
    return 0;
}

        运行结果:

3.3 静态成员函数实现异步回调(有线程)

        当有线程时,非静态成员函数无法实现函数的回调。需要使用静态成员函数进行回调。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
using namespace std;

//--------------
// 类中定义线程,并实现回调
// A应用
template <typename Tobject, typename Tparam>
class AsynA
{
    public:
        struct ThreadParam
        {
            AsynA *pthis;
            Tparam a;
        }; // 根据线程参数自定义结构
        typedef void (Tobject::*Cbfun)(Tparam);

    public:
        AsynA() : number(1)
        {
            printf("AsynA : AsynA()\n");
        }
        void print(void)
        {
            printf("print : number = %d \n", number);
        }
        static void *funCallbackThread(void *param); 
        void CreateCallbackThread(Tobject *pInstance, Cbfun pFun, Tparam a);
        
        void ThreadFunc(Tparam a);                   

    private:
        int number;
        Cbfun pCbfun;
        Tobject *m_pInstance;
};

template <typename Tobject, typename Tparam>
void AsynA<Tobject, Tparam>::CreateCallbackThread(Tobject *pInstance, Cbfun pFun, Tparam a)
{
    ThreadParam *p = new ThreadParam;
    p->pthis = this;
    p->a = a;
    m_pInstance = pInstance;
    pCbfun = pFun;

    pthread_t thing1;
    pthread_create(&thing1, NULL, &funCallbackThread, (void *)p);
}

template <typename Tobject, typename Tparam>
void *AsynA<Tobject, Tparam>::funCallbackThread(void *param)
{
    ThreadParam *p = (ThreadParam *)param;
    printf("ThreadParam p->a = %d\n", p->a);
    p->pthis->ThreadFunc(p->a);
    return 0;
}
template <typename Tobject, typename Tparam>
void AsynA<Tobject, Tparam>::ThreadFunc(Tparam a)
{
    printf("%d ThreadFunc : number = %d \n",number);
    while (1)
    {
        const pthread_t threadID = pthread_self();
        sleep(a);
        (m_pInstance->*pCbfun)((int)threadID);
    }
}
// B应用
class AsynB
{
    public:
        AsynB() : number(0)
        {
        }
        void fCallBack(int p)
        {
            printf("Thread ID = %ld\n", p);
        }
    private:
        int number;
};

int main(void)
{
    // 类中定义线程,并实现回调
    AsynA<AsynB, int> asyn_a;
    AsynB asyn_b;
    int p = 2;
    asyn_a.CreateCallbackThread(&asyn_b, &AsynB::fCallBack, p);

    AsynA<AsynB, int> asyn_a1;
    AsynB asyn_b1;
    int p1 = 4;
    asyn_a1.CreateCallbackThread(&asyn_b1, &AsynB::fCallBack, p1);
    
    // 主程序阻塞在这里
    getchar();
    return 0;
}

        运行结果:

        分析可知,上述代码完成在类中使用静态成员函数的异步回调,创建了两个子线程。

🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏⭐→关注🔍,如果能评论下就太惊喜了!
感谢大家的观看和支持!最后,☺祝愿大家每天有钱赚!!!

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

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

相关文章

【Spring实战】16 Profile

文章目录 1. 定义2. 使用2.1 定义 Profile2.2 激活 Profile 3. 演示3.1 properties文件3.2 打印日志3.3 启动服务&验证3.4 修改 active3.5 重启服务&验证 4. 应用场景4.1 数据库配置4.2 日志配置 5. 代码详细总结 Spring 框架提供了一种强大的机制&#xff0c;允许在不…

图像分割实战-系列教程9:U2NET显著性检测实战1

&#x1f341;&#x1f341;&#x1f341;图像分割实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 U2NET显著性检测实战1 1、任务概述

第7课 利用FFmpeg将摄像头画面与麦克风数据合成后推送到rtmp服务器

上节课我们已经拿到了摄像头数据和麦克风数据&#xff0c;这节课我们来看一下如何将二者合并起来推送到rtmp服务器。推送音视频合成流到rtmp服务器地址的流程如下&#xff1a; 1.创建输出流 //初始化输出流上下文 avformat_alloc_output_context2(&outFormatCtx, NULL, &…

Java EE Servlet之Cookie 和 Session

文章目录 1. Cookie 和 Session1.1 Cookie1.2 理解会话机制 (Session)1.2.1 核心方法 2. 用户登录2.1 准备工作2.2 登录页面2.3 写一个 Servlet 处理上述登录请求2.4 实现登录后的主页 3. 总结 1. Cookie 和 Session 1.1 Cookie cookie 是 http 请求 header 中的一个属性 浏…

AI 工具探索(二)

我参加了 奇想星球 与 Datawhale 举办的 【AI办公 X 财务】第一期&#xff0c;现在这是第二次打卡&#xff0c;也即自由探索&#xff0c;我选择 Modelscope 的 Agent 探索&#xff0c;并用gpts创作助理对比&#xff01; 最近想学学小红书的运营方法&#xff0c;选择了 小红书I…

【微服务】1.虚拟机配置

创建虚拟机选经典&#xff0c;其他配置同其他讲解文档 特殊注意 如果要自己设置IP地址&#xff0c;修改/etc/sysconfig/network-scripts/ 编辑ifcfg-ens33需改ip地址 #开机加载网络配置启动网络服务 ONBOOT"yes" #分配ip的协议 none static :不自动分配&#xff0c…

axios的使用及说明

目录 1.说明 2.直接使用 3.封装使用 4.注意 1.说明 官网&#xff1a;Axios 实例 | Axios中文文档 | Axios中文网 Axios 是一个基于 promise 网络请求库&#xff0c;作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使…

FL Studio 21最新版本for mac 21.2.2.3740中文解锁版2024最新图文安装教程

FL Studio 21最新版本for mac 21.2.0.3740中文解锁版是最新强大的音乐制作工具。它可以与所有类型的音乐一起创作出令人惊叹的音乐。它提供了一个非常简单且用户友好的集成开发环境&#xff08;IDE&#xff09;来工作。这个完整的音乐工作站是由比利时公司 Image-Line 开发的。…

redis容灾的方案设计

背景 今年各个大厂的机房事故频繁&#xff0c;其中关键组件Redis是重灾区&#xff0c;本文就来看下怎么做Redis的多机房容灾 Redis多机房容灾方案 1.首先最最直观的是直接利用Redis内部的主从数据同步来进行灾备&#xff0c;但是由于Redis内部的主从实现对机房间的网络延迟等…

2024 React 后台系统 搭建学习看这一篇就够了(1)

年初&#xff0c;自己想写一篇关于 React 实战后台项目的 课程文章&#xff0c;也算是对自己 2023的前端学习做一个系统性总结&#xff0c;方便后续查阅&#xff0c;也方便自己浏览&#xff0c;还能增加自己的文笔 网上很多平台都不太稳定&#xff0c;所以用了阿里的语雀&…

声明式导航传参详情

1 动态路由传参 路由规则path ->/article/:aid 导航链接 <router-link to"/article/1">查看第一篇文章</router-link> 组件获取参数: this.$route.params.aid 如果想要所有的值&#xff0c;就用this. $route. params 注意&#xff1a;这两个必须匹配…

实战入门 K8s剩下三个模块

1.Label Label是kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识&#xff0c;用来对它们进行区分和选择。 Label的特点&#xff1a; 一个Label会以key/value键值对的形式附加到各种对象上&#xff0c;如Node、Pod、Service等等 一个资源对象可以定义任意数量…

信创之国产浪潮电脑+统信UOS Linux操作系统体验10:visual studio code中调试C++程序

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、引言 老猿在CSDN的《信创之国产浪潮电脑统信UOS操作系统体验2&#xff1a;安装visual studio code和cmake搭建C开发环镜》介绍了在国产浪潮电脑统信UOS操作系统中安装visual studio code和cmake搭建C开…

2.3物理层下面的传输媒体

目录 2.3物理层下面的传输媒体2.3.1导引型传输媒体1.双绞线2.同轴电缆3.光纤 2.3.2非导引型传输媒体无线电微波通信 2.3物理层下面的传输媒体 传输媒体是数据传输系统中在发送器和接收器之间的物理通路 两大类&#xff1a; 导引型传输媒体&#xff1a;电磁波被导引沿着固体媒体…

linux下docker搭建Prometheus +SNMP Exporter +Grafana进行核心路由器交换机监控

一、安装 Docker 和 Docker Compose https://docs.docker.com/get-docker/ # 安装 Docker sudo apt-get update sudo apt-get install -y docker.io# 安装 Docker Compose sudo apt-get install -y docker-compose二、创建配置文件及测试平台是否正常 1、选个文件夹作为自建…

jenkins+pytest+allure

jenkinspytestallure allure下载地址 Releases allure-framework/allure2 GitHub allure环境变量配置 allure --version 查看版本(确定是否配置完成) python安装allure插件 pip install allure-pytest pytest的运行指令 pytest -sv test_demo.py 开发完毕后将代码上传到…

【Unity自制手册】基于Unity中物体移动相关方法和API集锦(动图详解)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

关于python解析mf4中二维信号数据的注意事项

python解析mf4中的信号数据一般用np.ndarray存储&#xff0c;但是mf4中的一个信号有时不一定是一维数据&#xff0c;有时会是一个二维的&#xff0c;没错&#xff0c;就是一个信号数据就是二维的&#xff0c;这时候&#xff0c;np数组的每个元素也是一个数组&#xff0c;这个时…

Docker support for NVIDIA GPU Accelerated Computing on WSL 2

Docker support for NVIDIA GPU Accelerated Computing on WSL 2 0. 背景1. 安装 Docker Desktop2. 配置 Docker Desktop3. WLS Ubuntu 配置4. 安装 Docker-ce5. 安装 NVIDIA Container Toolkit6. 配置 Docker7. 运行一个 Sample Workload 0. 背景 今天尝试一下 NVIDIA GPU 在…

服装店收银系统不只是收银 还需要线上商城和线上批发

一个综合性的服装店收银系统可以结合线上商城和线上批发功能&#xff0c;提供以下特点和优势&#xff1a; 线上商城&#xff1a;将服装店的商品信息同步到线上商城平台&#xff0c;让顾客可以通过网站或移动应用程序浏览和购买商品。线上商城可以实现在线支付、订单跟踪、售后服…