vlambda博客
学习文章列表

C语言基础之——回调函数


前言

在嵌入式物联网设备的开发中,一个项目软件由于其相对的复杂性,通常需要多人合作开发才能完成。以WIFI模块的二次开发举例,WIFI模块除了需要完成和主控MCU的通信,或者使用自己的外设驱动某些传感器,完成本地控制逻辑之外,还需要完成和云端通信的功能。一般在软件设计上,会分层处理,如分为两层:应用层和SDK层。将连接路由器、连接云端等部分作为SDK层,它是一个或多个组件或者是一个库,添加到项目里,对上层应用层只提供一个API,应用层使用这个API就可以实现连云的操作。对连云实现无需关心。

但如果SDK层需要用到应用层相关的API,来实现SDK层的功能,我们怎么做,才可以在不修改SDK的代码情况下,将这个API传入到SDK内以供调用呢?这就需要我们的回调函数登场了。

名词解释


以下取自百度百科:

回调方法是任何一个被以该回调方法为其第一个参数的其它方法调用的方法。很多时候,回调是一个当某些事件发生时被调用的方法。


百度百科的介绍比较难懂,简单的说就是,回调函数是由别人的函数执行时调用你实现的函数,打个比方:


以下是来自知乎作者常溪玲的解说:


函数指针的概念


回调函数是通过函数指针来实现的。

函数指针也是一种指针,只是它指向的不是整型,字符型而是函数。





函数指针的定义

/* 方法1 */void (*p_func)(intintfloat) = NULL//直接定义
/* 方法2 */typedef void (*tp_func)(intintfloat)//先声明后定义tp_func p_func = NULL;

这两种方式都是定义了一个指向返回值为 void 类型,参数为 (int, int, float) 的函数指针。


定义为什么不是 void ()(int, int, float) *p_func 而是 void (*p_func)(int, int, float) 这种形式?

这个是C标准的创建者制定的,只要记住是这个格式就可以了。


另外关于方法2的声明,C编译器非常清楚,typedef void (*tp_func)(int, int, float);这就是在声明一个void(*)()类型的函数指针tp_func.


函数指针的赋值


在定义完函数指针后,我们就需要给它赋值了,我们有两种方式对函数指针进行赋值:

void (*p_func)(int, int, float) = NULL;p_func = &func1;p_func = func2;

上面两种方法都是合法的。


将函数指针作为参数传给函数






函数指针和普通指针一样,我们可以将它作为函数的参数传递给函数,下面我们看看如何实现函数指针的传参:

/* func3 将函数指针 p_func 作为其形参 */void func3(int a, int b, float c, void (*p_func)(int, int, float)){ (*p_func)(a, b, c);}
/* func4 调用函数func3 */void func4(){ func3(1, 2, 3.0, func_1); /* 或者 func3(1, 2, 3.0, &func_1); */}


函数指针数组


和定义单个函数指针一样

/* 方法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) 的函数指针数组。


回调函数


我们前面谈的都是函数指针,现在我们回到正题,来看看回调函数到底是怎样实现的。下面是一个四则运算的简单回调函数例子:

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

这个例子有点长,我一步步地来讲解如何使用回调函数。


第一步

要完成加减乘除,我们需要定义四个函数分别实现加减乘除的运算功能,这几个函数就是:

/**************************************** * 加减乘除函数
***************************************/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;}

第二步

我们需要定义四个函数指针分别指向这四个函数:

/**************************************** * 函数指针结构体 ***************************************/typedef struct _OP { float (*p_add)(float, float);  float (*p_sub)(float, float);  float (*p_mul)(float, float);  float (*p_div)(float, float); } OP; 
/**************************************** * 初始化函数指针 ***************************************/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);}

第四步

当这几部都完成后,我们就可以开始调用回调函数了:

/* 调用回调函数 */  add_sub_mul_div(1.3, 2.2, DIV))printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",  add_sub_mul_div(1.3, 2.2, op->p_add),  add_sub_mul_div(1.3, 2.2, op->p_sub),  add_sub_mul_div(1.3, 2.2, MUL),

简单的四部便可以实现回调函数。在这四步中,我们甚至可以省略第二步,直接将函数名传入“库函数”,比如上面的乘法和除法运算。回调函数的核心就是函数指针,只要搞懂了函数指针再学回调函数,那真是手到擒来了。


结语


回调函数的核心就是函数指针,可以说函数指针是基础,回调函数是实战。使用回调函数是实现程序分层和解耦的重要一环。本节先了解和学习函数指针,再通过一个回调函数的例子来对回调函数进行熟悉和学习,方便理解。




QQ交流群:906015840 (备注:物联网项目交流)


静晨出品:静之所想,晨之所计