工程化编程实战callback接口学习笔记

本次实验任务:

  • 在VSCode下编译运行lab5-1.tar.gz 即http://pan.baidu.com/s/1pJ0qAIv

  • 通过VSCode+GDB调试程序找出quit命令无法运行的bug产生的原因

  • 分析callback接口的运行机制,总结callback接口设计的方法

实验环境:

  • Windows10+VS code+MinGW64

一、实验准备

  在这里可以下载相应平台的VS code并进行安装,用VS code打开从实验所用的c代码文件夹,发现无法识别头文件

 

  此时需要下载mingw-w64,mingw-w64用来对C/C++程序进行编译,以生成可执行文件,配合VS code进行代码的编写以及调试执行。

  你可以在这里进行mingw-w64的离线下载,为了节省篇幅,具体的安装过程可以参考这篇博客。

二、编译执行

  安装好后重启VS code,发现代码中头文件问题已经消失了,此时可以打开一个终端,执行gcc -o test menu.c linktable.c命令编译得到exe文件,编译发现menu.c文件中缺少string.h头文件

  添加头文件后重新执行gcc -o test menu.c linktable.c,得到test.exe,终端输入./test,执行,发现程序成功运行

工程化编程实战callback接口学习笔记_第1张图片

  继续运行quit命令,发现程序不能正常退出

三、调试

  回到代码中,在menu.c文件中找到main函数,发现当指针p==NULL时,控制台输出This is a wrong cmd,代码如下:

 1 while(1)
 2     {
 3         printf("Input a cmd number > ");
 4         scanf("%s", cmd);
 5         tDataNode *p = FindCmd(head, cmd);
 6         if( p == NULL)
 7         {
 8             printf("This is a wrong cmd!\n ");
 9             continue;
10         }
11         printf("%s - %s\n", p->cmd, p->desc); 
12         if(p->handler != NULL) 
13         { 
14             p->handler();
15         }
16    
17     }

  使用gdb调试,注意要想使用gdb调试,需要删除原来的test.exe文件,重新编译文件,加上-g参数,即gcc -o test menu.c linktable.c -g。随后修改launch.json文件,主要修改这两处

工程化编程实战callback接口学习笔记_第2张图片

  F5启动断点调试,监视p的值,发现输入quit后,p值返回为0x0,进入if判断,返回This is a wrong cmd!

工程化编程实战callback接口学习笔记_第3张图片

  指针p的值来自FindCmd()函数的返回值,定位到FindCmd()函数

1 tDataNode* FindCmd(tLinkTable * head, char * cmd)
2 {
3     return  (tDataNode*)SearchLinkTableNode(head,SearchCondition);
4 }

  函数体内调用了SearchLinkTableNode()函数,定位到SearchLinkTableNode()函数

 1 tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))
 2 {
 3     if(pLinkTable == NULL || Conditon == NULL)
 4     {
 5         return NULL;
 6     }
 7     tLinkTableNode * pNode = pLinkTable->pHead;
 8     while(pNode != pLinkTable->pTail)
 9     {    
10         if(Conditon(pNode) == SUCCESS)
11         {
12             return pNode;                    
13         }
14         pNode = pNode->pNext;
15     }
16     return NULL;
17 }

  在这里输入quit时返回的会是NULL,分析代码,可以看到while的判断条件是pNode != pLinkTable->pTail,查询p;InkTable结构体定义及引用,在如下红色代码处可知,pTail是链表的尾

 1 int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode)
 2 {
 3     if(pLinkTable == NULL || pNode == NULL)
 4     {
 5         return FAILURE;
 6     }
 7     pNode->pNext = NULL;
 8     pthread_mutex_lock(&(pLinkTable->mutex));
 9     if(pLinkTable->pHead == NULL)
10     {
11         pLinkTable->pHead = pNode;
12     }
13     if(pLinkTable->pTail == NULL)
14     {
15         pLinkTable->pTail = pNode;
16     }
17     else
18     {
19         pLinkTable->pTail->pNext = pNode;
20         pLinkTable->pTail = pNode;
21     }
22     pLinkTable->SumOfNode += 1 ;
23     pthread_mutex_unlock(&(pLinkTable->mutex));
24     return SUCCESS;        
25 }

  于是可知,quit不能正常执行是因为这里while的判断条件有问题,链表的最后一个节点没有得到判断,修改while循环停止的条件为pNode !=NULL,此时即可做到判断所有节点,删除test.exe文件,重新编译,调试运行,发现quit命令工作正常,不再进入判断

工程化编程实战callback接口学习笔记_第4张图片

四、回调函数分析

  本次实验代码中使用了回调函数,即tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))函数,什么是回调函数,知乎作者常溪玲是这样解说的:

  你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

  通过将函数指针作为另一个函数的参数而成为回调函数,回调函数就是通过函数指针调用的函数,本次代码中调用conditon应为回调函数,如果参数写为int(*Conditon)(tLinkTableNode * pNode))应当更加准确。

  通过callback函数,实现了程序的异步,在程序运行至需要函数值处时进行函数调用,拿到返回值。

  使用回调函数还有根据需要调用不同函数的好处,因为是函数指针作为函数形参,所以可以接受各种函数作为实参,只要被调用函数的参数符合即可,从而达到更大的灵活性。

  而之所以叫做回调函数,是因为在代码中,SearchLinkTableNode()函数调用了Conditon()函数,而Conditon()函数用调用了SearchLinkTableNode()给它的参数,它们相互调用,这就是回调!

 

你可能感兴趣的:(工程化编程实战callback接口学习笔记)