实验环境
本次实验的环境是Windows 10+VS Code,实验用到的代码先在Linux环境下解压再拷贝到Windows中。
编译、运行
先进入代码所在目录,然后输入指令“gcc -o test linktable.c menu.c”得到可执行文件“test.exe”
可以看到,我们的目录下生成了可执行文件。
运行“test.exe”,并键入“quit”
跟预期的一样,quit指令不能得到正确的响应。
纠错
查看menu.c的main函数可以发现,当指针p为空时,会显示“This is a wrong cmd!”,所以显然给p赋值的FindCmd函数返回值为空。
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(); } } }
再观察FindCmd函数,其具体内容如下:
tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition); }
该函数直接返回SearchLinkTableNode函数的调用结果,看来问题出在了SearchLinkTableNode上。
再查看SearchLinkTableNode函数代码如下:
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; }
观察代码,首先我们可以发现,当pLinkTable或Condition为空时,返回值为空。但是不幸的是我们遇到的不是这种情况。再往下看,我们可以发现,当遍历到pLinkTable的尾结点时,也直接返回空,而没有对尾结点的Condition值进行判断。而巧合的是,由InitMenuData函数我们可以看到,指令“quit”所在的节点恰好是链表的尾结点,所以就会出现不能正常相应“quit”指令的错误。
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; }
解决方法很简单,只需将while循环的条件改为“while(pNode != pLinkTable->pTail->pNext)”,使尾结点也被判断即可。
重新编译、运行代码,结果如下图
callback接口运行机制与设计方法
通常函数的参数是一些变量,比如menu.c中的FindCmd函数:tDataNode* FindCmd(tLinkTable * head, char * cmd);
但是有一些函数的参数中可能也有函数,如linktable.c中的SearchLinkTableNode函数:tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode));其有一个参数是函数int Conditon(tLinkTableNode * pNode),这个作为参数的Condition函数即为callback函数。callback函数的运行机制,以SearchLinkTableNode函数为例,只有在其中出现调用Condition函数的语句时才会调用Condition函数,也就是SearchLinkTableNode函数中的这一句——“if(Conditon(pNode) == SUCCESS)”。也就是说,Condition函数作为参数并不是一开始就传入的。callback函数设计方法无外乎定义callback函数,并使其成为另一个函数的参数。
callback函数的作用在我看来主要是对主调函数隐藏了其实现细节,且可以与主调函数分开编写。即主调函数需要某个功能的支撑,可以先用一个callback函数作为参数,callback函数具体的实现可以稍后再来完成。有点类似于C++中的类的public成员函数。