1. 函数指针说明
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
函数指针的定义形式为:
returnType (*pointerName)(param list);
returnType 为函数返回值类型,pointerName 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。
注意()的优先级高于*,第一个括号不能省略,
如果写作 returnType *pointerName(param list); 就成了函数原型,它表明函数的返回值类型为 returnType*
示例代码:
#include <stdio.h>
//返回两个数中较大的一个
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int x, y, maxval;
//定义函数指针
int (*pmax)(int, int) = max; //也可以写作int (*pmax)(int a, int b) = max
printf("Input two numbers:");
scanf_s("%d %d", &x, &y);
maxval = (*pmax)(x, y);
printf("Max value: %d\n", maxval);
return 0;
}
//运行结果:
Input two numbers:1 2
Max value: 2
2. 回调函数基于结构体的应用
经典用法:
c语言中,如何在结构体中实现函数的功能?把结构体做成和类相似,让他的内部有属性,也有方法,
这样的结构体一般称为协议类,提供参考:
struct {
int funcid;
char *funcname;
int (*funcint)(); /* 函数指针 int 类型*/
void (*funcvoid)(); /* 函数指针 void类型*/
};
每次都需要初始化,比较麻烦
#include <stdio.h>
typedef struct
{
int a;
void (*pshow)(int);
}TMP;
void func(TMP *tmp)
{
if(tmp->a >10)//如果a>10,则执行回调函数。
{
(tmp->pshow)(tmp->a);
}
}
void show(int a)
{
printf("a的值是%d\n",a);
}
void main()
{
TMP test;
test.a = 11;
test.pshow = show;
func(&test); //
}
终端显示:a的值是11
项目实例(基于串口透传):
头文件定义
typedef struct PassThroughProtoclPack
{
unsigned char en;
unsigned char cmd;
unsigned short alldatalen;
unsigned char *data;
unsigned short datalen;
unsigned char flag;
void (*PacketSend)(unsigned char *data,unsigned short datalen);
} T_PassThroughProtoclPack;
typedef struct PassThroughProtocl
{
unsigned char *pktbuf;
unsigned short pktbuflen;
unsigned short pktpos;
//unsigned short pktlentmp;
T_PassThroughProtoclPack *pkt;
void (*PacketCallBack)(T_PassThroughProtoclPack *PTP_pkt);
} T_PassThroughProtocl;
透传c文件定义
/*数据接收填充处理*/
void PassThroughProtolPutData(T_PassThroughProtocl *PTP_pck, unsigned char *data, unsigned short datalen)
{
unsigned short i = 0;
for (i = 0; i < datalen; i++)
{
if (PTP_pck->pktpos >= 1030)
{
PTP_pck->pktpos = 0;
PTP_pck->pktbuf[0] = 0;
}
// printf("\e[1;43m %02X\e[0m", data[i]);
if (PTP_pck->pktpos == 0 && data[i] == PTP_HEAD)
{
PTP_pck->pktbuf[PTP_pck->pktpos++] = PTP_HEAD;
// printf("PTP_pck->pktbuf[0]: %02x\n", PTP_pck->pktbuf[0]);
}
else if ((PTP_pck->pktpos > 0) && (PTP_pck->pktbuf[0] == PTP_HEAD))
{
PTP_pck->pktbuf[PTP_pck->pktpos++] = data[i];
if (PTP_pck->pktpos == 3)
PTP_pck->pkt->alldatalen = PTP_pck->pktbuf[2];
if ((PTP_pck->pktpos == (PTP_pck->pkt->alldatalen + 3)))
{
PTP_pck->pkt->cmd = PTP_pck->pktbuf[3];
PTP_pck->pkt->datalen = PTP_pck->pkt->alldatalen - 2;
if (PTP_pck->pkt->datalen > 0)
{
PTP_pck->pkt->data = &PTP_pck->pktbuf[4];
// printf("PTP_pck->pkt->data : %s\n", PTP_pck->pkt->data);
}
else
{
PTP_pck->pkt->data = NULL;
}
// printf("PTP_pck->pktbuf[PTP_pck->pktpos - 1] : %d\n", PTP_pck->pktbuf[PTP_pck->pktpos - 1]);
// printf("PTP_pck->pktpos3 : %d\n", PTP_pck->pktpos);
if (PTP_pck->pktbuf[PTP_pck->pktpos - 1] == PassThroughProtolBuildXor(0, &PTP_pck->pktbuf[1], PTP_pck->pktpos - 2))
{
PTP_pck->pkt->en = 1;
PTP_pck->PacketCallBack(PTP_pck->pkt); //2. 调用回调地址
}
PTP_pck->pktpos = 0;
PTP_pck->pktbuf[0] = 0;
}
}
else
{
PTP_pck->pktpos = 0;
PTP_pck->pktbuf[0] = 0;
}
}
}
初始化c文件定义
T_PassThroughProtoclPack pkt;
T_PassThroughProtocl ptppkt;
PassThroughProtolPutData(&ptppkt, data_buf, ret); //1. 串口接收调用触发结构体地址
//透传协议
void PacketCallBack(T_PassThroughProtoclPack *PTP_pkt) //3.回调触发
{
printf("rec one packet ........\r\n");
resolve_process(PTP_pkt);
}
int ptp_init(void)
{
pkt.PacketSend = uart_send;
ptppkt.pkt = &pkt;
ptppkt.pktbuf = pktbuf;
ptppkt.pktbuflen = COM_PACKAGE_LEN;
ptppkt.pktpos = 0;
ptppkt.PacketCallBack = PacketCallBack; //0. 回调地址赋值给结构体内定义的指针函数
return PassThroughProtolInit(&ptppkt);
}
一般回调函数的用法为:
甲方进行结构体的定义(成员中包括回调函数的指针),乙方定义结构体变量,并向甲方注册,甲方收集N个乙方的注册形成结构体链表,在某个特定时刻遍历链表,进行回调。
当函数指针做为函数的参数,传递给一个被调用函数,被调用函数就可以通过这个指针调用外部的函数,这就形成了回调,一般的程序中回调函数作用不是非常明显,可以不使用这种形式。
最主要的用途就是当函数不处在同一个文件当中,比如动态库,要调用其他程序中的函数就只有采用回调的形式,通过函数指针参数将外部函数地址传入来实现调用函数的代码作了修改,也不必改动库的代码,就可以正常实现调用便于程序的维护和升级。
3. 回调函数在库函数中的应用
在系统sdk开发中不能直接将底层相关逻辑暴露给应用层,应该封装成相应的回调接口以供上层调用,这样可以更好地解耦合,上层即不需要了解下层的细节。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
3.1 应用层回调使用
callback.c定义如下
#include <stdio.h>
typedef int (*lib_callback_fun_t)(char* str); //定义回调函数格式 为传入为char* return 为int的函数指针
int lib_printf_fun(char *str, lib_callback_fun_t callback);
/* 回调接口的定义 */
int lib_printf_fun(char *str, lib_callback_fun_t callback)//假设我是lib开发者(系统编程),在提供接口时,想给应用开发者(应用编程)执行它想要执行自定操作操作 提高灵活性
{
printf("This is lib function, printf main str: %s\n", str);
char* libstr = "this is lib str";
callback(libstr);
return 0;
}
/* 回调函数定义 */
int main_callback_print(char *str)
{
printf("This is main function,printf lib str: %s\n", str);
return 0;1
}
int main()
{
char* mainStr = "this is main str";
lib_printf_fun(mainStr, main_callback_print); //回调函数注册
return 0;
}
编译:
gcc callback.c -o test
这份代码功能即为 主函数会调用lib库开发人员提供的 lib_printf_fun
,并将回调函数地址传入约定好的lib函数中。 这里我们在回调函数中打印出lib传入值。这里就可以很清楚地看到mian函数调用了 lib_printf_fun()
,并调用时传入了自己定义的 main_callback_print()
这个函数。而这个函数在 lib_printf_fun()
中被调用,所以 main_callback_print()
就是回调函数。
3.2 回调接口定义封装成库函数调用
现在将上面这个函数拆成三个部分,并把lib函数分装成库函数。将库函数单独定义成一个.c和对应.h
callback_lib.c:
#include <stdio.h>
#include "callback_lib.h"
int lib_printf_fun(char *str, lib_callback_fun_t callback)//假设我是lib开发者(系统编程),在提供接口时,想给应用开发者(应用编程)执行它想要执行自定操作操作 提高灵活性
{
printf("This is lib function, printf main str: %s\n", str);
char* libstr = "this is lib str";
callback(libstr);
return 0;
}
callback_lib.h:
#ifndef _CALLBACK_LIB_H_
#define _CALLBACK_LIB_H_
typedef int (*lib_callback_fun_t)(char* str); //定义回调函数格式 为传入为char* return 为int的函数指针
int lib_printf_fun(char *str, lib_callback_fun_t callback);
#endif
callback.c
#include <stdio.h>
#include "callback_lib.h"
int main_callback_print(char *str)
{
printf("This is main function,printf lib str: %s\n", str);
return 0;
}
int main()
{
char* mainStr = "this is main str";
lib_printf_fun(mainStr, main_callback_print);
return 0;
}
这里把callback_lib.c 打包成静态库
gcc -c callback_lib.c
ar rcs -o libcall.a callback_lib.o
这样就得到了静态libcall.a 和callback_lib.h
使用libcall.a和我们主函数编译可支持文件
gcc -o run callback.c -L . -lcall
./run
This is lib function,printf main str: this is main str
This is main function,printf lib str: this is lib str
这样提供的代码的灵活性。当然上面这种回调是阻塞的。
4. 基于表驱动的编程
我们在数据逻辑处理中,一般会有很多的功能码,如果我们采用命令码和回调函数绑定的方式,那代码维护起来是不是很方便…
经典写法
void poll_task(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len)
{
switch (cmd){
case cmd1:
func1();
break;
case cmd2:
func2();
break;
case cmd3:
func3();
break;
case cmd4:
func4();
break;
default:
default_func();
break;
}
}
采用命令和回调函数绑定的方式
typedef struct
{
rt_uint8_t CMD;
rt_uint8_t (*callback_func)(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len);
} _FUNCCALLBACK;
_FUNCCALLBACK callback_list[]=
{
{ cmd1,func_callback1},
{ cmd2,func_callback2},
{ cmd3,func_callback3},
{ cmd4,func_callback41},
...
};
void poll_task(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len)
{
int cmd_indexmax = sizeof(callback_list) / sizeof(_FUNCCALLBACK);
int cmd_index = 0;
for (cmd_index = 0; cmd_index < cmd_indexmax; cmd_index++)
{
if (callback_list[cmd_index].CMD == cmd)
{
if(callback_list[cmd_index])
{
/*
处理逻辑
*/
}
}
}
}
参考链接:秒懂函数回调机制,回调函数看这篇就够了