软件工程课程实验报告:实验五

实验五:用callback增强链表模块来实现命令行菜单小程序V2.8

咖啡机《软件工程(C编码实践篇)》MOOC课程作业http://mooc.study.163.com/course/USTC-1000002006

新创建一个目录lab5完成实验。
然后将lab5-1.tar.gz中的代码(即解压后lab5.1/目录下的源文件)直接放到lab5/目录下继续完成后面的实验内容。


一、实验要求

  • 给lab5-1.tar.gz找bug,quit命令无法运行的bug
  • 利用callback函数参数使Linktable的查询接口更加通用
  • 注意接口的信息隐藏
  • 及时提交代码以防丢失

二、实验过程

1. 创建lab5文件夹,将lab5-1.tar.gz解压至文件夹下

软件工程课程实验报告:实验五_第1张图片

2. 编译代码并执行,发现quit() 命令无法正确执行

软件工程课程实验报告:实验五_第2张图片

3. 从程序的主入口入手,进行代码审查
  • menu.c中的main() 函数及InitMenuData(tLinkTable ** ppLinktable) 函数
int InitMenuData(tLinkTable ** ppLinktable)
{
    *ppLinktable = CreateLinkTable();
    tDataNode* pNode = (tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd = "help";
    pNode->desc = "Menu List:";
    pNode->handler = Help;
    AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);
    pNode = (tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd = "version";
    pNode->desc = "Menu Program V1.0";
    pNode->handler = NULL; 
    AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);
    pNode = (tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd = "quit";
    pNode->desc = "Quit from Menu Program V1.0";
    pNode->handler = Quit; 
    AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);

    return 0; 
}

/* menu program */

tLinkTable * head = NULL;

int main()
{
    InitMenuData(&head); 
   /* cmd line begins */
    while(1)
    {
        printf("Input a cmd number > ");
        scanf("%s", cmd);
        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();
        }

    }
}

可以看到,在main() 中,先初始化命令链表,InitMenuData(tLinkTable ** ppLinktable) 将quit命令的cmd和desc属性赋值。按照main() 的正常运行过程,输入quit,程序的输出应为”Quit from Menu Program v1.0”。

main() 中初始化命令链表之后,便是一个用来等待用户输入命令、并进行相关处理的循环体。在循环中,注意到输入的cmd通过FindCmd() 函数查找命令链表对应的结点p,p不为空时输出结点,p为空时提示错误”This is a Wrong cmd”,这恰与我们目前程序quit的输出相同。因此,错误可能来源于FindCmd() 函数。
- menu.c中的FindCmd()SearchCondition() 函数

int SearchCondition(tLinkTableNode * pLinkTableNode)
{
    tDataNode * pNode = (tDataNode *)pLinkTableNode;
    if(strcmp(pNode->cmd, cmd) == 0)
    {
        return  SUCCESS;  
    }
    return FAILURE;        
}

/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
    return  (tDataNode*)SearchLinkTableNode(head,SearchCondition);
}

FindCmd() 中可以看到,调用了linktable.c中的SearchLinkTableNode() 函数,其代码如下:

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))
{
    if(pLinkTable == NULL || Conditon == NULL)
    {
        return NULL;
    }
    tLinkTableNode * pNode = pLinkTable->pHead;
    while(pNode != pLinkTable->pTail)
    {    
        if(Conditon(pNode) == SUCCESS)
        {
            return pNode;                   
        }
        pNode = pNode->pNext;
    }
    return NULL;
}

函数中的循环体循环条件while(pNode != pLinkTable->pTail) 使得函数在到达命令链表尾部时,无法退出循环,无法访问最后一个结点。在这个程序中,最后一个结点就是quit命令。因此作以下修改(见代码中注释):

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))
{
    if(pLinkTable == NULL || Conditon == NULL)
    {
        return NULL;
    }
    tLinkTableNode * pNode = pLinkTable->pHead;
    //更改循环体条件,当链表结点不为空,便可继续遍历链表寻找命令
    while(pNode != NULL)
    {    
        if(Conditon(pNode) == SUCCESS)
        {
            return pNode;                   
        }
        pNode = pNode->pNext;
    }
    return NULL;
}
3. 再次编译运行,发现bug消失

软件工程课程实验报告:实验五_第3张图片

4. 解决数据结构的通用型
  • menu.c中

将全局变量cmd[CMD_MAX_LEN]放入main()函数内

int main()
{
    InitMenuData(&head);
    //降低模块间的耦合
    char cmd[CMD_MAX_LEN]; 
   /* cmd line begins */
    while(1)
    {
        printf("Input a cmd number > ");
        scanf("%s", cmd);
        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();
        }

    }
}

修改SearchCondition()的参数,传入cmd变量。

int SearchCondition(tLinkTableNode * pLinkTableNode,void * args)
{
    char *cmd = (char *)args;
    tDataNode * pNode = (tDataNode *)pLinkTableNode;
    if(strcmp(pNode->cmd, cmd) == 0)
    {
        return  SUCCESS;  
    }
    return FAILURE;        
}

修改FindCmd()函数:

/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
    return  (tDataNode*)SearchLinkTableNode(head, SearchCondition, (void *) cmd);
}

在linktable.c中修改SearchLinkTableNode()函数:

tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void *args), void * args)
{
    if(pLinkTable == NULL || Conditon == NULL)
    {
        return NULL;
    }
    tLinkTableNode * pNode = pLinkTable->pHead;
    while(pNode != NULL)
    {
        if(Conditon(pNode, args) == SUCCESS)
        {
            return pNode;
        }
        pNode = pNode->pNext;
    }
    return NULL;
}
5. 隐藏重要数据结构实现
  • linktable.h中
    在linktable.h接只提供结构体类型的声明,将结构体类型的定义放在linktable.c中。
/*
 * LinkTable Node Type
 */
typedef struct LinkTableNode tLinkTableNode;

/*
 * LinkTable Type
 */
typedef struct LinkTable tLinkTable;
6. 编译运行

软件工程课程实验报告:实验五_第4张图片

7. 将代码同步到github

软件工程课程实验报告:实验五_第5张图片

Github地址:https://github.com/973301529/se/tree/master/lab5

三、实验总结

通过本次实验,软件开发过程中需要具备模块化思想,掌握了接口函数的作用。对于callback函数进行了简单的实践,体会到了它的优势。

你可能感兴趣的:(软件工程实验报告)