在第一篇博客中提到了全新的程序框架,我们会大量的使用回调函数,其中包括枚举类型、结构体、函数指针的应用。
回调函数:就是一个通过函数指针调用的函数。如果你把函数的地址传递给中间函数的形参,中间函数通过函数指针调用其所指向的函数时,我们就说这是回调函数。如:
Fun1() //应用层
{
Fun2(Fun3); //Fun2中间层,Fun3回调函数
}
通俗点的解释:函数Fun1调用函数Fun2,同时将函数Fun3作为形参传递给Fun2,此时,Fun1为应用层函数,Fun2为中间层函数,Fun3为回调函数,回调函数是一种说法而已。当Fun2被调用时,Fun3也会被接着调用。
这样编程的意义在于:
利于代码结构,将代码分为应用层,中间层,硬件驱动层,彼此独立,方便程序的编辑,阅读,修改与移植;
结构化编程时,结构体只需要定义中间函数,减小内存的开销。
伪代码1
Fun1()
{
Fun2()
{
Fun3_1();
Fun3_2();
…………
}
}
这种函数调用写法的缺陷:
当Fun3有多个功能函数时,每次增加一个,都要修改Fun2,这样代码的耦合性就大。
通过函数指针的好处是,Fun2与Fun3实现隔离,比如Fun3具有多个功能函数,增加或减少时,不需要修改Fun2的代码。
伪代码2
LED.h
定义带有三个函数指针的结构体类型
//定义结构体类型
typedef struct
{
LED_Num_t LED_Num;
void (*LED_ON)(uint8_t );
void (*LED_OFF)(uint8_t );
void (*LED_Flip)(uint8_t );
}LED_t;
extern LED_t LED;
LED.c
初始化结构体,每个函数指针都指向一个函数
LED_t LED =
{
LED1,
LED_ON,
LED_OFF,
LED_Flip
};
main.c
通过结构体调用函数
int main()
{
LED.LED_ON();
LED.LED_OFF();
}
这种函数调用写法的缺陷:
1、如果功能函数增多,那结构体里面的函数指针也要增多,增加内存开销
2、main函数或其他调用函数要修改,main函数想要调用增多的功能函数,也要增加调用的操作
回调函数调用关系
回调函数代码使用示例
原本LED.h头文件里的结构体
//定义结构体类型
typedef struct
{
void (*LED_ON)(uint8_t );
void (*LED_OFF)(uint8_t );
void (*LED_Flip)(uint8_t );
}LED_t;
修改后
本来结构体里是三个分别指向 LED_ON、LED_OFF、LED_Flip的函数指针,现在改为只定义一个中间层的函数指针LED_Fun,该指针名字为LED_Fun,可指向一个函数,该函数无返回值,一个参数类型为uint8_t,另一个参数为一个函数指针(指向一个无返回值,参数类型为uint8_t的函数)
本来 LED_ON、LED_OFF、LED_Flip函数是用 static 修饰为静态函数的,只能通过结构体变量进行调用,现在用回调函数后,通过函数指针调用,所以要在源文件中把这三个函数的 static 去掉,并在头文件中加 extern 声明为外部可调用。(当初我和UP主一起写代码的时候,没有注意到static的用法,程序一直报错,原来是这样啊,受教了!)
//定义枚举类型
typedef enum
{
LED1 = (uint8_t)0x01,
LED2 = (uint8_t)0x02,
LED3 = (uint8_t)0x03
}LED_Num_t;
//定义结构体类型
typedef struct
{
void (*LED_Fun)(uint8_t,void (*CallBack)(uint8_t)); //中间层函数
}LED_t;
/* extern variables-----------------------------------------------------------*/
extern LED_t LED;
extern void LED_ON(uint8_t ); //函数声明为外部可调用
extern void LED_OFF(uint8_t );
extern void LED_Flip(uint8_t );
/* extern function prototypes-------------------------------------------------*/
LED.c
源文件里实现中间函数LED_Fun(),直接调用CallBack指针指向的函数;在结构体里初始化LED_Fun函数。
/* Private variables----------------------------------------------------------*/
static void LED_Fun(uint8_t ,void (*CallBack)(uint8_t));
/* Public variables-----------------------------------------------------------*/
LED_t LED =
{
LED_Fun
};
/* Private function prototypes------------------------------------------------*/
/*
* @name LED_Fun
* @brief LED功能函数,中间虚拟函数(回调函数)
* @param LED_NUM:LED灯编号,CallBack:回调函数指针
* @retval None
*/
static void LED_Fun(uint8_t LED_NUM,void (*CallBack)(uint8_t))
{
(*CallBack)(LED_NUM);
}
main函数或其他函数使用
STA_Machine.c状态机源文件通过结构体变量LED,调用LED_Fun函数,并传入第一个参数LED灯编号,然后第二个参数就传入具体实现功能的函数,因为函数名就等于函数的首地址,所以把LED_OFF函数的首地址给到了函数指针CallBack,当LED_Fun函数被调用,则LED_OFF函数也会紧接着被调用,从而实现回调函数机制。
/*
* @name Fun_STA1
* @brief 状态函数1
* @param None
* @retval None
*/
static void Fun_STA1()
{
HAL_Delay(500); //延时500ms
LED.LED_Fun(LED1,LED_OFF); //熄灭LED1 LED.LED_Fun(LED2,LED_OFF); //熄灭LED2
LED.LED_Fun(LED3,LED_OFF); //熄灭LED3
STA_Machine.ucSTA_Machine_Status = STA2; //切换到STA2状态
}