C~回调函数

news2024/11/20 16:32:31

还是c的学习啊

一、函数指针

在讲回调函数之前,我们需要了解函数指针。

我们都知道,C语言的灵魂是指针,我们经常使用整型指针,字符串指针,结构体指针等

int *p1;
char *p2;
STRUCT *p3; //STRUCT为我们定义的结构体

但是好像我们一般很少使用函数指针,我们一般使用函数都是直接使用函数调用。

下面我们来了解一下函数指针的概念和使用方法。

1.概念

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

函数指针可以像一般函数一样,用于调用函数、传递参数。

函数指针的定义方式为:

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

“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。

我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(指针变量名)”。但是这里需要注意的是:“(指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。

那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“”,如果有“”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。

最后需要注意的是,指向函数的指针变量没有 ++ 和 – 运算。

一般为了方便使用,我们会选择

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

比如

typedef int (*Fun1)(int);//声明也可写成int (*Fun1)(int x),但习惯上一般不这样。
typedef int (*Fun2)(int, int);//参数为两个整型,返回值为整型
typedef void (*Fun3)(void);//无参数和返回值
typedef void* (*Fun4)(void*);//参数和返回值都为void*指针

2,如何用函数指针调用函数

给大家举一个例子:

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/
p = &Func;          /*将Func函数的首地址赋给指针变量p*/

赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。

下面来写一个程序,看了这个程序你们就明白函数指针怎么使用了:

#include <stdio.h>
int Max(int, int);  //函数声明
int main(void)
{
    int(*p)(int, int);  //定义一个函数指针
    int a, b, c;
    p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通过函数指针调用Max函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}
int Max(int x, int y)  //定义Max函数
{
    int z;
    if (x > y)
    {
        z = x;
    }
    else
    {
        z = y;
    }
    return z;
}

特别注意的是,因为函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址。

p = Max可以改成 p = &Max
c = (*p)(a, b) 可以改成 c = p(a, b)

3.函数指针作为某个函数的参数

既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。

示例:

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

typedef void(*FunType)(int);
//前加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量。
//形式同 typedef int* PINT;
void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
    callFun(myFun,100);//传入函数指针常量,作为回调函数
    callFun(hisFun,200);
    callFun(herFun,300);

    return 0;
}

void callFun(FunType fp,int x)
{
    fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}

void myFun(int x)
{
    printf("myFun: %d\n",x);
}
void hisFun(int x)
{
    printf("hisFun: %d\n",x);
}
void herFun(int x)
{
    printf("herFun: %d\n",x);
}

输出: 

 

4.函数指针作为函数返回类型

有了上面的基础,要写出返回类型为函数指针的函数应该不难了,下面这个例子就是返回类型为函数指针的函数:

void (* func5(int, int, float ))(int, int)
{
    ...
}

在这里, func5 以 (int, int, float) 为参数,其返回类型为 void (\*)(int, int)。在C语言中,变量或者函数的声明也是一个大学问,想要了解更多关于声明的话题,可以参考我之前的文章 - C专家编程》读书笔记(1-3章)。这本书的第三章花了整整一章的内容来讲解如何读懂C语言的声明。

5.函数指针数组

在开始讲解回调函数前,最后介绍一下函数指针数组。既然函数指针也是指针,那我们就可以用数组来存放函数指针。下面我们看一个函数指针数组的例子:

/* 方法1 */
void (*func_array_1[5])(int, int, float);

/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];

上面两种方法都可以用来定义函数指针数组,它们定义了一个元素个数为5,类型是 *void (\*)(int, int, float)*的函数指针数组。

6.函数指针总结

  1. 函数指针常量 :Max;函数指针变量:p;

  2. 数名调用如果都得如(*myFun)(10)这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许myFun(10)这种形式地调用(这样方便多了,并与数学中的函数形式一样)。

  3. 函数指针变量也可以存入一个数组内。数组的声明方法:int (*fArray[10]) ( int );

二、回调函数

1.什么是回调函数

我们先来看看百度百科是如何定义回调函数的:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

这段话比较长,也比较绕口。下面我通过一幅图来说明什么是回调:

 

假设我们要使用一个排序函数来对数组进行排序,那么在主程序(Main program)中,我们先通过库,选择一个库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。

结合这幅图和上面对回调函数的解释,我们可以发现,要实现回调函数,最关键的一点就是要将函数的指针传递给一个函数(上图中是库函数),然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是C语言特有的,几乎任何语言都有回调函数。在C语言中,我们通过使用函数指针来实现回调函数。

我的理解是:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调

如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2 为什么要用回调函数?

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

int Callback()    ///< 回调函数
{
    // TODO
    return 0;
}
int main()     ///<  主函数
{
    // TODO
    Library(Callback);  ///< 库函数通过函数指针进行回调
    // TODO
    return 0;
}

回调似乎只是函数间的调用,和普通函数调用没啥区别。

但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。

这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。

3 怎么使用回调函数?

int Callback_1(int a)   ///< 回调函数1
{
    printf("Hello, this is Callback_1: a = %d ", a);
    return 0;
}

int Callback_2(int b)  ///< 回调函数2
{
    printf("Hello, this is Callback_2: b = %d ", b);
    return 0;
}

int Callback_3(int c)   ///< 回调函数3
{
    printf("Hello, this is Callback_3: c = %d ", c);
    return 0;
}

int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
    Callback(x);
}

int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

如上述代码:可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()/Callback_2()/Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。

4.下面是一个四则运算的简单回调函数例子:

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

/****************************************
 * 函数指针结构体
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

/****************************************
 * 加减乘除函数
 ***************************************/
float ADD(float a, float b) 
{
    return a + b;
}

float SUB(float a, float b) 
{
    return a - b;
}

float MUL(float a, float b) 
{
    return a * b;
}

float DIV(float a, float b) 
{
    return a / b;
}

/****************************************
 * 初始化函数指针
 ***************************************/
void init_op(OP *op)
{
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}

/****************************************
 * 库函数
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    return (*op_func)(a, b);
}

int main(int argc, char *argv[]) 
{
    OP *op = (OP *)malloc(sizeof(OP)); 
    init_op(op);
    
    /* 直接使用函数指针调用函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2), 
            (op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));
     
    /* 调用回调函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", 
            add_sub_mul_div(1.3, 2.2, ADD), 
            add_sub_mul_div(1.3, 2.2, SUB), 
            add_sub_mul_div(1.3, 2.2, MUL), 
            add_sub_mul_div(1.3, 2.2, DIV));

    return 0; 
}

5. 回调函数实例(很有用)

一个GPRS模块联网的小项目,使用过的同学大概知道2G、4G、NB等模块要想实现无线联网功能都需要经历模块上电初始化、注册网络、查询网络信息质量、连接服务器等步骤,这里的的例子就是,利用一个状态机函数(根据不同状态依次调用不同实现方法的函数),通过回调函数的方式依次调用不同的函数,实现模块联网功能,如下:

/*********  工作状态处理  *********/
typedef struct
{
 uint8_t mStatus;
 uint8_t (* Funtion)(void); //函数指针的形式
} M26_WorkStatus_TypeDef;  //M26的工作状态集合调用函数


/**********************************************
** >M26工作状态集合函数
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{    
    {GPRS_NETWORK_CLOSE,  M26_PWRKEY_Off  }, //模块关机
    {GPRS_NETWORK_OPEN,  M26_PWRKEY_On  }, //模块开机
    {GPRS_NETWORK_Start,   M26_Work_Init  }, //管脚初始化
    {GPRS_NETWORK_CONF,  M26_NET_Config  }, /AT指令配置
    {GPRS_NETWORK_LINK_CTC,  M26_LINK_CTC  }, //连接调度中心  
    {GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC  },  //等待调度中心回复 
    {GPRS_NETWORK_LINK_FEM, M26_LINK_FEM  }, //连接前置机
    {GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM  }, //等待前置机回复
    {GPRS_NETWORK_COMM,  M26_COMM   }, //正常工作    
    {GPRS_NETWORK_WAIT_Sig,  M26_WAIT_Sig  },  //等待信号回复
    {GPRS_NETWORK_GetSignal,  M26_GetSignal  }, //获取信号值
    {GPRS_NETWORK_RESTART,  M26_RESET   }, //模块重启
}
/**********************************************
** >M26模块工作状态机,依次调用里面的12个函数   
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
    uint8_t i = 0;
    for(i = 0; i < 12; i++)
    {
        if(Start == M26_WorkStatus_Tab[i].mStatus)
        {          
      return M26_WorkStatus_Tab[i].Funtion();
        }
    }
    return 0;
}

所以,如果有人想做个NB模块联网项目,可以copy上面的框架,只需要修改回调函数内部的具体实现,或者增加、减少回调函数,就可以很简洁快速的实现模块联网。

whaosoft aiot http://143ai.com   

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

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

相关文章

win10下docker安装

业精于勤荒于嬉&#xff0c;总是与偷懒做斗争。 很多年了&#xff0c;一直不愿做事情&#xff0c;不愿意想事情。 1、安装 Docker Toolbox -- Install on Windows | Docker Documentation http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/DockerToolbox-…

今年你们赚到钱了吗?

峥嵘的2022年&#xff0c;各位程序员们&#xff0c;你们赚到钱了吗&#xff1f; 今天是2022年12月21日&#xff0c;眼看就快过年了&#xff01; 今年你们赚到钱了吗&#xff1f;对于我而言&#xff0c;又是一个"窘迫/囧迫"的穷年&#xff0c;有点心慌慌&#xff0c;有…

windows安装Elasticsearch

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索&#xff0c;具有接近实时的搜索&#xff0c;并支持多租户。Elasticsearch是分布式的&#xff…

纷享销客《华东地区新增长专刊》重磅发布

增长&#xff0c;是企业发展永恒的主题。 纷享销客的使命是用创新科技和行业智慧赋能企业增长。在服务超过5000中大型客户的基础上&#xff0c;我们发现&#xff0c;各行各业从营销端到流通端&#xff0c;再到生产端&#xff0c;已然出现了全渠道、全场景、端到端、一体化融合…

【战“疫”案例展】壹点灵心理服务平台——携手抗疫,共克时艰“星光”同行公益项目...

‍网易云信战“疫”案例本项目由网易云信投递并参与“数据猿行业盘点季大型主题策划活动—— #榜样的力量# 《新冠战“疫”——中国数据智能产业最具社会责任感企业》榜单/奖项”评选。‍数据智能产业创新服务媒体——聚焦数智 改变商业自2019年底以来&#xff0c;人们的情绪持…

电脑垃圾站刚清空的文件怎么找回?总结了三种方法

当电脑上删除文件后会临时存放在电脑垃圾站中&#xff0c;如果还需要这些文件&#xff0c;还能有“后悔药”&#xff0c;直接在电脑垃圾站里面找到进行“还原”即可&#xff0c;但是当垃圾站的文件刚被清空了怎么办&#xff1f;如何找回文件呢&#xff1f;下面分享三种方法恢复…

赛狐ERP|假期模式补货建议,解决春节备货难题

马上就到春节假期了&#xff0c;有两个让亚马逊卖家特别头疼的难题&#xff1a; 工厂放假停工 拿不到货 尤其今年不同于往年&#xff0c;各种因素的影响让供应商的放假时间特别早&#xff0c;工厂放假停工以及年后生产排期的不确定性&#xff0c;会导致卖家很长一段时间都无法…

activemq安装windows环境,数据订阅与发布,生产者与消费者

官网下载&#xff0c;选择适合本地jdk编译环境的部署包 安装完成后解压到指定目录&#xff0c;目录不要包含中文&#xff0c;否则可能启动不成功。 解压完成后找到指定路径 双击即可运行 &#xff0c;见到如下界面启动成功 访问本地管理页面http://127.0.0.1:8161/ 输入账号…

SpringBoot全局异常处理

1、异常处理的种类 我大概把一次请求分成三个阶段&#xff0c;来分别进行全局的异常处理。 在进入Controller之前&#xff0c;譬如请求一个不存在的地址&#xff0c;404错误。在执行RequestMapping时&#xff0c;进入逻辑处理阶段前。譬如传的参数类型错误。以上都正常时&…

升级Win11系统卡在了35%怎么解决?

升级Win11系统卡在了35%怎么解决&#xff1f;Windows11是微软开发的电脑的最新操作系统&#xff0c;不少人都会选择更新到Windows11&#xff0c;但是进行更新安装对很多人来讲并不是一件很简单的事情&#xff0c;不少人都在抱怨安装时常常卡在35%或85%&#xff0c;并且带有一个…

Java.Util复习贴

参加了数次竞赛之后&#xff0c;我发现我的瓶颈所在——语法。于是今天来复习一下常用的Java库函数吧。 比赛中用java8还是10我就不care啦&#xff0c;我直看官方最新文档。地址在 https://docs.oracle.com/javase/10/docs/api/java/util/package-summary.html 首先看到java.…

排序——插入排序、希尔排序

目录 一.插入排序 1.实现 2.时间复杂度 二.希尔排序 2.预排序 (1).单次预排序的实现 (2).相对有序 2.代码 一.插入排序 1.实现 正如其名&#xff0c;是将第n1个数据插入到前面的n的升序&#xff08;降序&#xff09;数据中&#xff0c;形成一个n1大小的升序&#xff0…

用于宏观经济数据分析的神经网络(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 宏观经济时间序列的分析和预测是国家决策者非常感兴趣的因素。然而&#xff0c;由于缺乏精确的经济模型和外部因素&#xff08…

浏览器隐藏滚动条(不影响内容滚动)

系列文章目录 文章目录系列文章目录一、背景和效果图如下&#xff1a;1.背景2.设置属性前效果图&#xff1a;2.设置后效果图&#xff1a;二、直接通过CSS修改样式&#xff0c;保存滑动功能1.全局设置滚动条如下&#xff08;所有的都被隐藏&#xff09;Css代码如下效果图如下2.给…

DP1363F高度集成的非接触读写芯片 13.56M NFC/RFID读卡器芯片 兼容替代CLRC663

DP1363F高度集成的非接触读写芯片 13.56M NFC/RFID读卡器芯片 兼容替代CLRC663 DP1363F是一款高度集成的非接触读写芯片&#xff0c;集强大的多协议支持、最高射频输出功率&#xff0c;以及突破性技术低功耗卡片检测等优势于一身&#xff0c;满足市场对更高集成度、更小外壳和…

Mybatis 异常总结

java.sql.SQLSyntaxErrorException: #42000 一般发生在修改 删除中 原因可能是 传入的参数与 sql 语句不匹配 It’s likely that neither a Result Type nor a Result Map was specified 异常分析&#xff1a; 关键在第一段提示&#xff1a; It’s likely that neither a Re…

Yolov5移植树莓派4B问题总结

Hallo&#xff0c;大家好啊&#xff01;之前出过几篇文章关于Yolov5的&#xff0c;不得不说Yolov5的识别率真的很高&#xff0c;对个体检测很有帮助&#xff0c;如果大家训练完之后会发现获得一个pt文件&#xff0c;这就是训练好的模型。但是&#xff0c;这个模型只能够在自己的…

Golang 【basic_leaming】2 语言容器

阅读目录Go 语言数组_定义_初始化_遍历定义数组Go 语言初始化数组Go 语言遍历数组参考文献Go 语言切片&#xff08;Slice&#xff09;初始化_删除元素_遍历什么是切片声明切片使用 make() 函数构造切片使用 append() 函数为切片添加元素从数组或切片生成新的切片从指定范围中生…

Unreal Engine中的UHT和UBT

UBT&#xff1a;附加在引擎之外的一个自定义工具&#xff0c;用于管理跨各种构建配置&#xff0c;来配置和构建UE源码的过程。 UHT&#xff1a; 目录 UBT&#xff08;UnrealBuilderTool&#xff09; UHT&#xff08;UnrealHeadTool&#xff09; UBT&#xff08;UnrealBuilder…

《第三堂棒球课》:MLB棒球创造营·棒球名人堂

田中将大 田中将大(Tanaka Masahiro)&#xff0c;1988年11月1日出生于兵库县伊丹市&#xff0c;日本职业棒球运动员。 在2006年日本职棒高中生选秀会上被东北乐天金鹰队第一指选中&#xff0c;此后开始职业生涯。在2014年被纽约洋基队以上亿巨额签约&#xff0c;期限为七年。…