C语言回调函数详解(全网最全)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

}

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);
}


输出:


![img](https://img-blog.csdnimg.cn/img_convert/164e01fac4c2050ae770516b585f6ff9.png)


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


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



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

}


在这里, **`func5`** 以 **`(int, int, float)`** 为参数,其返回类型为 **`void (\*)(int, int)`** 。在C语言中,变量或者函数的声明也是一个大学问,想要了解更多关于声明的话题,可以参考我之前的文章 - [C专家编程》读书笔记(1-3章)](https://bbs.csdn.net/topics/618679757)。这本书的第三章花了整整一章的内容来讲解如何读懂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.什么是回调函数


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



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


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


![回调机制](https://img-blog.csdnimg.cn/img_convert/a418b49a00b25664225ee16e01749117.png)


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


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


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


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


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


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


#### 2 为什么要用回调函数?


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


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


![img](https://img-blog.csdnimg.cn/img_convert/be9215d0098db4522bb7d4784ebdf054.png)



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();

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

外链图片转存中…(img-NUCXoGnI-1715866016497)]
[外链图片转存中…(img-HWc1NnMH-1715866016498)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值