1 命令解析器

将用户输入的命令解析到对应的动作的系统,另一种说法是状态机。根据维基百科上的定义

有限状态机(finite-state machine, FSM,简称状态机)是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型

本文重点是实现C语言面向对象式的写法,并尝试对比分析这种写法和传统的面向过程式的编程有哪些个区别。

 

2 作用

根据我的理解,命令解析器实现用户通过命令码与系统进行交互,进而调用函数改变系统的当前状态,与传统的顺序式程序处理相比,这种交互式的处理更加灵活。

 

3 同类框架

<1>面向过程,使用switch-case实现命令解析器,通过外部声明调用模块内函数

C语言面向对象式编程之命令解析器_C语言面向对象

 

<2>面向对象,使用C语言结构体绑定命令码和处理函数,通过函数指针访问模块内函数

C语言面向对象式编程之命令解析器_C语言面向对象_02

 

对比

两种写法在功能实现上都没有毛病,但是对比上边两种写法,有分析得出

面向过程式(方法一) 面向对象式(方法二)
命令增加引起跨模块修改(分别在函数实现和调用的位置修改) 命令增加只要在模块内部进行实现和注册即可
大量的外部函数,模块间高耦合 解耦合,模块之间基本独立,通过函数指针访问静态的内部函数

除了上述好处,面向对象的实现中还讲命令相关的(命令码和对应的处理函数)封装到一个结构体中,实现了两者的绑定,而不用我们在程序中再考虑这层关系。

 

4 面向对象写法实现

数据类型
<1>命令结构体类型,用于绑定命令码和处理函数

typedef struct cmd
{
 	char cmd_name[MAX_CMD_NAME_LENGTH + 1];   //命令码
	handler cmd_operate;			  	      //命令操作的方法
} CMD;


<2>命令列表,用线性表来实现,用于存放命令的机制

typedef struct cmds
{
 	CMD cmds[MAX_CMDS_COUNT];                 //cmd对象数组
	int num;	                              //当前命令个数标记
} CMDS;

 

相关操作
<1>批量注册命令

/******************************************************************************
* 函数介绍:命令注册函数
* 输入参数:reg_cmds 要注册的命令数组, num命令数量
* 输出参数:无
* 返回值:无
* 备注:num <= MAX_CMDS_COUNT
******************************************************************************/
void register_cmds(CMD reg_cmds[], int num)
{
	int i;

	if (num > MAX_CMDS_COUNT)
	{
	 	return;
	}

	for (i = 0;i < num;i++)
	{
		if (commands.num < MAX_CMDS_COUNT)	//命令列表未满
		{
			strcpy(commands.cmds[i].cmd_name, reg_cmds[i].cmd_name);	//注册命令码
			commands.cmds[i].cmd_operate = reg_cmds[i].cmd_operate;	//注册命令处理

			++commands.num;	//命令个数+1		
		}

	}
}


<2>匹配命令码,并执行对应函数

/******************************************************************************
* 函数介绍:命令码匹配执行函数
* 输入参数:str 待匹配的命令码
* 输出参数:无
* 返回值:无
* 备注:str长度不能大于MAX_CMD_NAME_LENGTH
******************************************************************************/
void match_cmd(const char *str)
{
	int i;

	if (strlen(str) > MAX_CMD_NAME_LENGTH)
	{
		return;
	}

	for (i = 0;i < commands.num;++i)
	{
	 	if (strcmp(commands.cmds[i].cmd_name, str) == 0)
		{
		 	commands.cmds[i].cmd_operate();		//执行方法
		}
	}

}

 

源码链接:https://gitee.com/hinzer/command_parser/tree/master/object_oriented/seqlist_cmds

 

5 面向对象写法改进(使用链表来存放命令)

好处是链表可以动态的增加节点,不用考虑命令数量上限问题。

数据类型
<1>命令结构体,用于绑定命令码和处理函数

typedef struct cmd
{
 	char cmd_name[MAX_CMD_NAME_LENGTH + 1];   //命令码
	handler cmd_operate;			  	      //命令操作的方法
} CMD;


<2>命令列表,链表实现,用于存放命令的机制

typedef struct node
{
 	CMD m_cmd;    		             			//cmd对象数组
	struct node* m_next;								//下一个节点
} CMD_ListNode;

 

相关操作
<1>批量注册命令

/******************************************************************************
* 函数介绍:命令注册函数
* 输入参数:reg_cmds 要注册的命令数组, num命令数量
* 输出参数:无
* 返回值:true 成功, false 失败
* 备注:num <= MAX_CMDS_COUNT
******************************************************************************/
bool register_cmds(CMD reg_cmds[], int num)
{
	int i;
	if (NULL == cmd_pHead)
	{
		return false;
	}

	for (i = 0;i < num;i++)
	{
		list_head_insert(reg_cmds[i]);	//将命令码和方法作为节点信息插入链表
	}

	return true;
}


<2>匹配命令码,并执行对应函数

/******************************************************************************
* 函数介绍:命令码匹配执行函数
* 输入参数:str 待匹配的命令码
* 输出参数:无
* 返回值:true 成功, false 失败
* 备注:str长度不能大于MAX_CMD_NAME_LENGTH
******************************************************************************/
bool match_cmd(const char *str)
{
	CMD_ListNode* H = cmd_pHead;

	if ((NULL == str) || (NULL == cmd_pHead))
	{
		return false;
	}

	if (strlen(str) > MAX_CMD_NAME_LENGTH)
	{
		return false;
	}

	//链表不空
	while(H)
	{
	 	if (strcmp(H->m_cmd.cmd_name, str) == 0)
		{
		 	H->m_cmd.cmd_operate();		//执行方法
		}
		H = H->m_next;
	}

	return true;

}

 

源码链接:https://gitee.com/hinzer/command_parser/tree/master/object_oriented/list_cmds


5 运行结果
C语言面向对象式编程之命令解析器_框架_03

 

 

6 参考源

视屏教程:《C语言大型软件设计的面向对象》

邵国际: C 语言对象化设计实例 —— 命令解析器