• 回调函数和表驱动法编程


    回调函数和表驱动法编程

    回调函数

    回调函数其实就是在一个函数里面调用了另一个函数,而调用哪个函数是由调用回调函数的人决定,举个简单例子:

    int add(int a,int b)
    {
        return a+b;
    }
    int sub(int a,int b)
    {
        return a-b;
    }
    typedef int (*pfunc)(int a,int b);
    int func_callback(pfunc p,int a,int b)
    {
    	return(*p)(a,b);
    }
    void main(void)
    {
        func_callback(add,1,2);//return 3
        func_callback(sub,2,1);//return 1
    }
    

    这里的func_callback就是回调函数,它的执行结果根据传进来的函数指针p的实际指向函数不同而不同,即传入加法add得到的是加法计算,传入减法sub得到的是减法计算结果。

    回调函数的使用使得上层调用时仅需要同一个接口(func_callback)即可,而根据传入的参数不同而调用到不同的底层结果。

    例如add和sub是操作系统或BSP提供的一个功能。我们希望上层应用在调用时具有拓展性,即如果底层新增了新的功能例如乘除法(mul,div),我们无需修改上层应用的接口,仅需在传入参数时增加新的参数(mul,div)即可。这样底层提供的API具有了通用性,应用层无需修改调用api的接口即可增加新的使用方式。

    回调函数还有一个很好用的功能是和表驱动法结合:

    表驱动法

    表驱动法顾名思义就是类似数据库查表的方式实现功能,简单地说,将程序中的分支判断变为查表操作,简单例子:

    char getCharfromHex(unsigned char ucNum)
    {
        char ucNumChar = 0;
        if(ucNum < 10)
        {
            ucNumChar = ucNum + '0';
        }
        else if(ucNum == 10)
        {
            ucNumChar = 'A';
        }
        else if(ucNum == 11)
        {
            ucNumChar = 'B';
        }
        else if(ucNum == 12)
        {
            ucNumChar = 'C';
        }
        else if(ucNum == 13)
        {
            ucNumChar = 'D';
        }
        else if(ucNum == 14)
        {
            ucNumChar = 'E';
        }
        else if(ucNum == 15)
        {
            ucNumChar = 'F';
        }
        else
        {
        	return 0;    
        }
        return ucNumChar;
    }
    

    上面的操作是将一个16进制数变为ASCII表示的字符,如果使用表驱动,则可以实现为:

    CHAR aNumChars[] = {'0', '1', '2', /*3~9*/'A', 'B', 'C', 'D', 'E', 'F'};
    CHAR ucNumChar = aNumChars[ucNum % sizeof(aNumChars)];
    

    这样一个较多的if else或者switch case语句变为简单的查表操作。

    使用表驱动法,则可以将很多功能接口进行排列后按查表方式进行执行,当功能接口较多时能够比switch语句更加简洁。

    由表驱动和回调函数组成的通用功能接口

    首先将对外的功能进行一个排列,构造一个功能接口表,并对功能接口编码(dispatch.h):

    typedef enum {
    	ChipCheck = 0x41,
    	DataTrans = 0x01,
    	/*others*/
    	DataSave = 0x77,
    } FunctionCode_t;
    

    根据功能函数的格式,声明一个函数指针(dispatch.h):

    typedef u32 (*pFunc)(u8 *cmd_in, u8 *cmd_out);
    

    实现功能函数,注意功能函数的接口尽量保持一致,可以通过传入一个通用的结构体或指针数组方式,具体功能处理上的区别接口内部对数据进行细分处理。

    头文件(dispatch.h):

    u32 func_ChipCheck(u8 *cmd_in, u8 *cmd_out);
    u32 func_DataTrans(u8 *cmd_in, u8 *cmd_out);
    u32 func_DataSave(u8 *cmd_in, u8 *cmd_out);
    

    源文件(dispatch.c):

    u32 func_ChipCheck(u8 *cmd_in, u8 *cmd_out)
    {
    	/*do something*/
    	PRINT_WARN("func %s,line num: %d, %p,%d!
    ", __FUNCTION__, __LINE__, cmd_in, 2);
    	return (0);
    }
    
    u32 func_DataTrans(u8 *cmd_in, u8 *cmd_out)
    {
    	/*do something*/
    	PRINT_WARN("func %s,line num: %d, %p,%d!
    ", __FUNCTION__, __LINE__, cmd_in, 2);
    	return (0);
    }
    u32 func_DataSave(u8 *cmd_in, u8 *cmd_out)
    {
    	/*do something*/
    	PRINT_WARN("func %s,line num: %d, %p,%d!
    ", __FUNCTION__, __LINE__, cmd_in, 2);
    	return (0);
    }
    

    建立索引关系,即functionlist中的功能接口和funcCodelist的接口名称一一对应起来,同时编写索引查找函数。在源文件进行定义:

    pFunc functionlist[] = {
    	func_ChipCheck,
    	func_DataTrans,
    	func_DataSave,
    };
    
    u8 funcCodelist[] =
    {
    	ChipCheck,
    	DataTrans,
    	DataSave,
    };
    /**
     * 功能接口的回调函数
     *
     * @author KingBoy (2020/5/24)
     *
     * @param p 被调用的函数
     * @param cmd_in 输入参数
     * @param cmd_out 输出参数
     *
     * @return u32 0-success
     */
    u32 dispath_callback(pFunc p, u8 *cmd_in, u8 *cmd_out)
    {
    	u32 ret = 0;
    	ret = (*p)(cmd_in, cmd_out);
    	return (ret);
    }
    /**
     * 功能派发接口
     *
     * @author KingBoy (2020/5/23)
     *
     * @param cmd_ptr 输入输出:数据起始地址
     * @param cmd_len 输入输出:数据长度
     */
    void function_dispatch(u8 *cmd_ptr, u32 *cmd_len)
    {
    	int func_code;
    
    	func_code = getEnumIndex(*(cmd_ptr));
    	PRINT_WARN("func %s,line num: %d, %02x
    ", __FUNCTION__, __LINE__, func_code);
    	if (func_code != -1)
    	{
    		//用法1,直接定义函数数组后调用
    		//functionlist[func_code](cmd_ptr, cmd_ptr);
    		//用法2,使用回调函数进行处理
    		dispath_callback(functionlist[func_code], cmd_ptr, cmd_ptr);
    	}
    	else
    	{
    		PRINT_WARN("func %s,line num: %d, %02x
    ", __FUNCTION__, __LINE__, func_code);
    	}
    }
    /**
     * 获取功能的索引位置
     *
     * @author KingBoy (2020/5/23)
     *
     * @param value 功能码值
     *
     * @return int 索引位置,-1为未找到
     */
    int getEnumIndex(u8 value)
    {
    	int i = 0;
    	for (i = 0; i < sizeof(funcCodelist); i++)
    	{
    		if (value == funcCodelist[i])
    		{
    			return (i);
    		}
    	}
    	if (i >= sizeof(funcCodelist))
    	{
    		return (ERR_NOFUNC);
    	}
    
    	return (0);
    }
    

    function_dispatch即处理的函数,cmd_ptr将外部数据传入,根据第一个字节的取值决定是执行哪个功能函数(*(cmd_ptr)),在getEnumIndex中获得该功能的索引位置,如果查到的位置合法(不是-1),则调用回调函数dispath_callback并将需要调用的功能函数functionlist[func_code]传入,同时传入函数的参数(u8 *cmd_in, u8 *cmd_out)。

    对于相似的功能接口来说,可以选择直接调用(functionlist[func_code](cmd_ptr, cmd_ptr);)或者是回调函数调用(dispath_callback(functionlist[func_code], cmd_ptr, cmd_ptr);)两者的处理等价。

    如果使用回调函数,当想要修改功能时,可以只在dispath_callback进行操作而不需要对底层进行修改。

  • 相关阅读:
    MVC部分视图
    windows服务
    mvc中seeeion和cook的用法
    @Html.Raw()用法和Html.ActionLink的用法总结
    FileStream类的使用(文件流)
    简单工厂和工厂模式对比
    项目案例【Net Core】如何注入多个服务实现类
    快速查找所有存储过程/触发器中是否包含某个字符串
    TortoiseGit安装与配置
    C# 中==与Equals方法比较
  • 原文地址:https://www.cnblogs.com/RegressionWorldLine/p/12945403.html
Copyright © 2020-2023  润新知