2017 Fall Software Engineering Learning (6)
模块化 命令行菜单
公开发表一篇实验报告,并在实验报告中注明【网易云课堂昵称 + 《软件工程(C编码实践篇)》MOOC课程作业http://mooc.study.163.com/course/USTC-1000002006 】
一、实验需求分析
- 为menu子系统设计接口,并写用户范例代码来实现原来的功能;
- 使用make和make clean来编译程序和清理自动生成的文件;
- 使menu子系统支持带参数的复杂命令,并在用户范例代码中自定义一个带参数的复杂命令;
- 可以使用getopt函数获取命令行参数
二、程序思路分析
我们在上次的lab5代码的基础上进行修改(如果不清楚上次lab5实现了什么可以看看我之前的实验5)。
首先,我们将menu模块化,这就意味着我们将原来的main函数的内容用子函数实现并且定义接口,同时由于原来的menu程序是在menu.c文件里面定义的函数具体操作,所以说修改命令的具体内容就要在menu.c文件里面。但是为了向用户隐藏具体的实习细节,我们希望用户能够不要操作我们提供的源文件。那么我们就还需要一个函数来添加命令,用户能在自己的main函数里面调用这个函数向menu模块添加命令。
实现了上述功能之后就是让我们的命令支持参数输入,就像之前的main函数一样。这里我们只用修改一下menu模块,对于输入的字符串,我们用strtok解析一下,然后传递给相应的函数handler就可以了。
下面是实现上述功能的子函数模块:
int MenuConfig(char *cmd, char *desc, int (*handler)())
//为命令链表添加命令
int GetOpt(char *pcmd, int *argc, char *argv[])
//从pcmd这一输入的字符串中将命令、参数提取出来
int ExecuteMenu()
//menu主模块,原来的main函数
三、程序具体实现
代码量有点小多,所以我就不贴上来啦。这里就介绍一下主要的几个函数模块,其他内容与实验5 的内容一样
int MenuConfig(char *cmd, char *desc, int (*handler)())
{
tDataNode* pNode = NULL;
if (head == NULL)
{
head = CreateLinkTable();
pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = "help";
pNode->desc = "Menu List:";
pNode->handler = Help;
AddLinkTableNode(head,(tLinkTableNode *)pNode);
}
pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = cmd;
pNode->desc = desc;
pNode->handler = handler;
AddLinkTableNode(head,(tLinkTableNode *)pNode);
return 0;
}
给命令列表添加命令的模块。
nt GetOpt(char *pcmd, int *argc, char *argv[])
{
if (pcmd == NULL || argc == NULL || argv == NULL)
{
printf("Wrong in GetOpt, invalid arguments\n");
return FAILURE;
}
pcmd = strtok(pcmd," ");
while(pcmd != NULL && *argc < CMD_MAX_ARGV_NUM)
{
argv[*argc]= pcmd;
(*argc)++;
pcmd = strtok(NULL," ");
}
if (*argc >= 1)
{
*(argv[*argc-1]+strlen(argv[*argc-1])-1)='\0';
}
return SUCCESS;
}
函数从pcmd中提取命令和参数,将参数数目存在argc中,参数具体内容存在字符串数组argv中。注意argc要用指针,不然无法将内容传递出去。
int ExecuteMenu()
{
//InitMenuData(&head);
/* cmd line begins */
char cmd[CMD_MAX_LEN];
while(1)
{
int argc = 0;
char *argv[CMD_MAX_ARGV_NUM];
char *pcmd = NULL;
printf("Input a cmd number > ");
//scanf("%s", cmd);
pcmd = fgets(cmd, CMD_MAX_LEN, stdin);
if(pcmd == NULL)
{
continue;
}
if (GetOpt(pcmd, &argc, argv) == FAILURE)
{
printf("Wrong in ExecuteMenu, GetOpt failure\n");
return FAILURE;
}
tDataNode *p = FindCmd(head, cmd);
if( p == NULL)
{
printf("This is a wrong cmd!\n ");
continue;
}
printf("%s - %s\n", p->cmd, p->desc);
if(p->handler != NULL)
{
p->handler(argc,argv);
}
}
return SUCCESS;
}
就是命令行菜单的主函数,其他的与实验5 区别不大。这里主要就时调用了GetOpt来解析参数,而且将gets换成了fgets,这是因为gets有数组超界的隐患,可能会导致内存泄漏。
接下来就是我的程序了:
/*******************************************************************************
* Author : acrididcheng
* Email : chenghuaming@aliyun.com
* First Created : 2017-11-04 23:32
* Filename : test.c
* Description :
* *****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "menu.h"
#include "linktable.h"
int Quit(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: quit [-h/-t]\n");
return FAILURE;
}
if (strcmp(argv[1],"-h") == 0)
{
;
}
if (strcmp(argv[1],"-t") == 0)
{
printf("Bye~~^_^\n");
}
exit(0);
}
int Operate(int argc, char *argv[])
{
if (argc != 4)
{
printf("Usage: operate ['+'/'-'/'*'/'/'] [double1] [double2]\n");
return FAILURE;
}
if (strcmp(argv[1],"+") == 0)
{
printf("the output is:%f\n",(atof(argv[2])+atof(argv[3])));
return SUCCESS;
}
else if (strcmp(argv[1],"-") == 0)
{
printf("the output is:%f\n",(atof(argv[2])-atof(argv[3])));
return SUCCESS;
}
else if (strcmp(argv[1],"*") == 0)
{
printf("the output is:%f\n",(atof(argv[2])*atof(argv[3])));
return SUCCESS;
}
else if (strcmp(argv[1],"/") == 0)
{
printf("the output is:%f\n",(atof(argv[2])/atof(argv[3])));
return SUCCESS;
}
else
{
printf("wrong operation\n");
return FAILURE;
}
return FAILURE;
}
int main()
{
MenuConfig("version","menu Independent version 1.0.",NULL);
MenuConfig("quit","quit from the menu",Quit);
MenuConfig("operate","do some math calculation",Operate);
ExecuteMenu();
return 0;
}
可以看见我写了一个quit()函数和一个operate()函数。
这里为了测试功能,我将quit变成了加参数才能退出的函数,而且添加了一个具体的功能命令–operate,来实现double的基本运算(+,-,*,/)。
这个程序我就不讲了,超简单。有兴趣就自己看一下代码,很短。
哦,还有最后一个,要写一个Makefile文件,这个我在之前的几个实验里面都有用过,在写实验2的时候有过较详细的介绍。这个Makefile的代码变种特别多,所以这里就不再介绍了。有兴趣的可以去make的wiki看看各种语法。
四、程序效果演示
make一下后就可以运行了,这里可以发现,功能还是没什么大问题的。
运行完后make clean 清理一下就行了:
五、实验总结
这次讲解了参数的处理和整体模块化,让整个程序完整了,符合了尽可能模块化,精简main函数的思想,而且这次用到了makefile,使得整个工程圆满结束。非常有意义!!