IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 函数指针数组之LOG处理系统 -> 正文阅读

[游戏开发]函数指针数组之LOG处理系统

通过前面的介绍,我们知道,嵌入式软件基本上是一个由外部激励驱动的系统。因此,在实现上有大量需要根据激励(包括收到的数据、用户输入的命令、有限状态机迁移等)的不同类型进行分门别类处理的情况。C语言提供了两种直接的机制可以用于处理这种情况:if/else和switch/case。一般对于分支较少的情况(一般3、4个以下),可以选用if/else;对于稍微多一些的情况(一般5、6个以下),可以选用switch/case。但是对于更多分支的情况,用这两种方式都不直观,影响可读性和可扩展性。本章主要介绍处理这种情况的一种有效方式—函数指针数组。

函数指针数组是元素为函数指针的一类数组。其形式一般为:

typedef void (*Function_Pointer_t)(int iParameter);

void Function1(int iParameter);

void Function2(int iParameter);

void Function3(int iParameter);

Function_Pointer_t funcPointerArray[] = {

? Function1,

? Function2,

? Function3,

};

首先定义了一个函数指针类型Function_Pointer_t,其形参为一个正数iParameter,返回值类型为void。同时有三个函数Function1、Function2、Function3,与上述函数指针类型Function_Pointer_t,具有相同的形参类型和返回值类型。然后定义了一个函数指针数组funcPointerArray,其类型为Function_Pointer_t,有三个元素,分别为:Function1、Function2、Function3。然后可以数组元素的形式调用相应的函数,比如:

(funcPointerArray[0])(iPara);

调用的就是Function1(iPara)。

下面通过实际的例子来说明函数指针数组的应用。本篇介绍一个嵌入式系统中经常需要用到的LOG输出系统的实现,下一篇介绍一个Modbus的协议处理的实现。

LOG命令处理系统

在一般的嵌入式软件中,为了调试和问题定位的方便,都实现了相应的LOG输出或保存功能。但如果LOG系统保持常时运行,会在Flash使用寿命、耗电、运行性能等方面带来不利影响,因此,在实际运行中LOG系统经常是关闭的。如果在现场发现了问题,往往希望随时激活LOG系统,取得分析问题所必要的信息。激活LOG的方式一般有两种:一种方法是采用编译选项,只在调试版中包含LOG相关的代码,而在实际运行版本(发布版)中则不包含。这样虽然可以使得发布版尽量精简,但需要采集LOG信息时,只能重新下载调试版程序。这增加了操作的难度(比如有些设备在现场难以下载程序),而且有时重新下载程序也会导致重现问题的环境丢失。第二种是采用在线命令的方式激活,也即在发布版中也包含LOG相关的代码,但平时运行时,不保存或输出LOG;只有在需要采取LOG时,才通过命令的形式激活LOG功能。虽然这使得发布版的代码增大,但可以随时激活/关闭LOG功能,大大提高了现场问题解决的便利性。

LOG命令一般通过UART与用户交互,在PC侧利用通用的串口工具(如Windows自带的超级终端)等,以命令行的形式输入命令。利用该接口,除了可以开关LOG,也可以设定工作模式、串口波特率等。

另外,作为调试工具,往往先实现个别命令,然后在使用中逐渐添加,因此要求比较好的可扩展性。

在实现这样一个系统时,主要是对不同的命令进行处理:包括识别命令、执行相应的处理。假如有以下几条命令:

  • logon??????????????????????? ??—打开LOG输出
  • logoff??????????????????????????—关闭LOG输出
  • setlogmode b/B/t/T? ? ?—设置LOG输出模式,指定b、B为二进制输出,t、T为文本输出。
  • setrate baudrate? ? ? ? ?—设置波特率

因为需要对不同的命令分别处理,比较直观的实现方式是采用if/else或者switch/case,以下是用if/else实现时的代码:

void handleLogCommands(char *pcACommand) {

? //Check the parameters.

? if(NULL == pcACommand)

??? return; //Nothing to do.

? if(0 == memcmp(pcACommand, "logon", 5)) {

??? handleLogOn();

? }

? else if(0 == memcmp(pcACommand, "logoff", 6)) {

??? handleLogOff();

? }

? else if(0 == memcmp(pcACommand, "setlogmode", 10)) {

??? handleLogModeSetting(pcACommand + 10);

? }

? else if(0 == memcmp(pcACommand, "setrate", 7)) {

??? handleBaudrateSetting(pcACommand);

? }

? else {

??? //Illegal command.

??? return;

? }

}

通过判定输入命令行前若干个字符是否为支持的命令,调用相应的命令处理函数。在该例子中,处理各个命令的函数handleLogOn(),handleLogOff(),handleLogModeSetting(),handleBaudrateSetting()的具体实现没有给出。这种实现方式在命令数少的时候没有问题,但随着命令数量的增加,这个函数的可读性下降。而且handleLogCommands ()作为LOG命令处理的控制函数,与各个命令的处理耦合:需要增加/删除命令时,就需要修改该控制函数。


通过引入函数指针数组,就可以解耦控制函数和各个命令的处理,使命令的增减不影响控制函数。

char *pcCommands = {

? "logon",

? "logoff",

? "setlogmode",

? "setrate",

};

#define COMMANDS_NUM?? (sizeof(pcCommands) / sizeof(pcCommands[0]))

typedef void (*Command_Handler_t)(char *pcCommand);

Command_Handler_t commandHandlers[] = {

? handleLogOn,

? handleLogOff,

? handleLogModeSetting,

? handleBaudrateSetting,

};

void handleLogCommands(char *pcACommand) {

? uint8_t ucStep;

? //Check the parameters.

? if(NULL == pcACommand)

??? return; //Nothing to do.

? for(ucStep = 0; ucStep < COMMANDS_NUM; ucStep++) {

??? if(0 == strcmp(pcCommands[ucStep], pcCommand, strlen(pcCommands[ucStep]))) {

????? commandHandlers[ucStep](pcACommand);

????? break;

??? }

? }

}

在以上的实现中,命令定义数组和命令处理的函数指针数组是分开的。在命令增加/删除的时候,需要修改这两个数组,并实现相应的命令处理函数即可,不需要修改控制函数handleLogCommands()。但在命令增删的时候,需要同时修改两个数组,容易造成两个数组不一致。比如从命令数组中已经删除了某条命令,但处理函数被保留了,导致程序执行错误。为了避免这种问题,我们可以将命令及其处理放在一个数组中,如以下代码所示:

typedef void (*Command_Handler_t)(char *pcCommand);

typedef struct {

? char?????????????????????????????? *pcCommand;

? Command_Handler_t? aHandler;

} Command_List_t;

Command_List_t? pCommandsList = {

? {"logon", handleLogOn },

? {"logoff", handleLogOff },

? {" setlogmode ", handleLogModeSetting },

? {" setrate ", handleBaudrateSetting },

};?

#define COMMANDS_NUM???? (sizeof(pCommandsList) / sizeof(pCommandsList[0]))

void handleLogCommands(char *pcACommand) {

? uint8_t ucStep;

? //Check the parameters.

? if(NULL == pcACommand)

??? return; //Nothing to do.

? for(ucStep = 0; ucStep < COMMANDS_NUM; ucStep++) {

??? if(0 == strcmp(pCommandsList [ucStep]. pcCommand, pcCommand, strlen(pCommandsList [ucStep]. pcCommand))) {

????? pCommandsList [ucStep]. aHandler(pcACommand);

????? break;

??? }

? }

}

通过定义一个由指向命令的指针与其处理函数构成结构,并用其定义一个结构数组,从而将命令和其处理函数捆绑在了一起。这样在增删命令的时候只需要改动一处,就不易出错。

作为一般的原则,互相关联、存在内在联系的元素(变量、函数等)应该通过定义结构,尽量放在一起。比如在上面的例子中那样,将其定义在一个结构中。这就可以保证命令和命令处理函数同时存在,而且保持一致。

通过以上的说明可以看到,通过引入函数指针数组,解除了控制函数与具体命令处理函数的耦合。不但提高了程序的可读性,也使系统的扩展非常方便。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:52:21  更:2022-05-05 11:55:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 11:36:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码