zoukankan      html  css  js  c++  java
  • 工程化编程实战callback接口学习笔记

     

    VSCode配置

      为了使用VSCode调试功能,需要配置launch.json和tasks.json文件,使得VSCode可以编译并启动调试。

      在VSCode中打开lab5.1文件夹,并打开其中的menu.c文件。此时按下Ctrl+Shift+B进行编译,下面的终端会提示编译错误的信息,主要意思是缺少定义。因为menu.c调用了linktable.c中的函数,而默认生成的编译模板只是将自身文件(即menu.c)加入到编译的文件列表中,因此需要修改编译模板即tasks.json文件,将linktable.c手动加入到编译文件列表中。

      点击工具栏上的Terminal,选择Configure Tasks -> C/C++:gcc build active file,VSCode会自动生成适用于gcc编译的tasks.json文件,在第12行原有的内容后面增加"${fileDirname}/linktable.c",,注意不要忘记最后的逗号。最终的tasks.json内容如下:

     1 {
     2     // See https://go.microsoft.com/fwlink/?LinkId=733558 
     3     // for the documentation about the tasks.json format
     4     "version": "2.0.0",
     5     "tasks": [
     6         {
     7             "type": "shell",
     8             "label": "gcc build active file",
     9             "command": "/usr/bin/gcc",
    10             "args": [
    11                 "-g",
    12                 "${file}","${fileDirname}/linktable.c",
    13                 "-o",
    14                 "${fileDirname}/${fileBasenameNoExtension}"
    15             ],
    16             "options": {
    17                 "cwd": "/usr/bin"
    18             },
    19             "problemMatcher": [
    20                 "$gcc"
    21             ],
    22             "group": "build"
    23         }
    24     ]
    25 }

      保存后继续进行编译,还有一个warning信息,strcmp函数没有显式声明,这是因为strcmp函数是在string.h头文件中声明的,因此在menu.c中需要包含string.h文件。修改好后编译成功,没有错误和警告信息。

      可以正确编译后,需要配置VSCode的调试功能,这里使用默认的调式模板即可。因此按下F5,选择C++(GDB/LLDB) -> gcc build and debug active file开始调试,VSCode自动生成launch.json文件,并进入调试界面。launch.json的内容如下:

     1 {
     2     // Use IntelliSense to learn about possible attributes.
     3     // Hover to view descriptions of existing attributes.
     4     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
     5     "version": "0.2.0",
     6     "configurations": [
     7         {
     8             "name": "gcc build and debug active file",
     9             "type": "cppdbg",
    10             "request": "launch",
    11             "program": "${fileDirname}/${fileBasenameNoExtension}",
    12             "args": [],
    13             "stopAtEntry": false,
    14             "cwd": "${workspaceFolder}",
    15             "environment": [],
    16             "externalConsole": false,
    17             "MIMode": "gdb",
    18             "setupCommands": [
    19                 {
    20                     "description": "Enable pretty-printing for gdb",
    21                     "text": "-enable-pretty-printing",
    22                     "ignoreFailures": true
    23                 }
    24             ],
    25             "preLaunchTask": "gcc build active file",
    26             "miDebuggerPath": "/usr/bin/gdb"
    27         }
    28     ]
    29 }

    调试程序

      按下F5进入调试界面,先不设置断点运行程序。首先输入"help"显示帮助信息,程序可以正确识别该命令,并输出3个支持的命令,分别为:“help”、"version"和"quit"。  

    接着输入"version",看到程序可以正确识别该命令并输出信息。

    接着输入"quit",却提示是一个错误的命令,显然程序出错了。

      首先找到输出该错误信息的代码位置,发现在main函数中。程序的main函数内容如下:

     1 int main()
     2 {
     3     InitMenuData(&head); 
     4    /* cmd line begins */
     5     while(1)
     6     {
     7         printf("Input a cmd number > ");
     8         scanf("%s", cmd);
     9         tDataNode *p = FindCmd(head, cmd);
    10         if( p == NULL)
    11         {
    12             printf("This is a wrong cmd!
     ");
    13             continue;
    14         }
    15         printf("%s - %s
    ", p->cmd, p->desc); 
    16         if(p->handler != NULL) 
    17         { 
    18             p->handler();
    19         }
    20    
    21     }
    22 }

      该错误提示在12行,发现当p指针为NULL时会执行该行代码,向上看发现p指针为函数FindCmd的返回值,而FindCmd函数其实是调用的linktable.c中的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 }

      该函数仅在两种情况下会返回NULL,分别在第5行和第16行,分别对应函数传入的参数错误和while循环中Conditon函数没有SUCCESS两种情况。很显然,由于其他的命令可以正确执行,第一种情况可以排除。因此把调试的精力主要放在第二种情况上。

      因此将调试的断点设在第8行的while循环处(原文件中为149行),打开调试窗口(Ctrl+Shift+D),在终端输入quit命令后程序自动在while循环入口处暂停。如下图所示:

      点击上方调试工具栏的图标或者按下F11进入循环体,继续按下F11进入到Conditon函数,该函数实际上是menu.c中的SearchCondition函数。如下图所示,pNode->cmd值为"help",与需要的"cmd"不同,显然返回的是FAILURE。

      继续进行调试。第二次进入while循环后,当前节点已经指向下一个节点了,也就是"version"节点,同样不是我们需要的值。从Conditon返回后,pNode指针指向链表中的下一个节点,也就是我们需要的"quit'节点。但是在执行while中的条件判断语句时,发现不满足进入循环的条件而直接跳过循环返回NULL了,显然问题出在条件判断语句。

      仔细查看该判断语句,仅当pNode与pLinkTable->pTail不相等时才进入该循环体,而pTail指向的是链表的最后一个节点,也就是说链表的最后一个节点内容不会被遍历到,而quit正好是链表中的最后一个节点,自然也就无法执行其对应的退出操作。因此我们需要修改while函数的判断条件。该处代码的本意是遍历所有的链表节点,因此只需要将判断条件变为pNode != NULL。因为链表的最后一个节点的next指针指向NULL,当遍历完最后一个节点后,pNode变为了NULL,自然无法满足判定条件而退出循环。修改后的代码如下,仅修改了第8行的判断条件:

     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 != NULL)
     9     {    
    10         if(Conditon(pNode) == SUCCESS)
    11         {
    12             return pNode;                    
    13         }
    14         pNode = pNode->pNext;
    15     }
    16     return NULL;
    17 }

      重新编译运行,所有的命令都可正常运行,也能识别出错误的命令。

     callback接口的运行机制

     回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

       以上是百度百科中对回调函数的说明,简单来讲,就是程序对外提供一个服务(接口),由用户去决定该服务的具体内容(回调函数)。就比如说航空公司的值机服务,航空公司提供了该服务,并且只需要知道客户是否已经值机,而不需要知道客户是通过何种方式进行值机的(柜台、手机、官网等)。值机服务就类似于callback接口。

      以本次代码中的SearchLinkTableNode函数为例,该函数的其中一个参数Conditon即为一个callback接口。它实际接收的是FindCmd函数传递过来的指向SearchCondition函数的函数指针。这样,对于链表的搜索函数,在实现过程中不需要去具体实现找到节点的方法,只要接收回调函数返回的是否匹配的信息即可,使得搜索函数的适用性更加广泛,实现起来也更加简单。

  • 相关阅读:
    wx-charts 微信小程序图表 -- radarChart C# .net .ashx 测试
    C# dev SearchLookUpEdit 和 RepositoryItemSearchLookUpEdit 测试
    C# Dev XtraReport 简单测试
    SQL查询,关联查询的区别 (LEFT JOIN 、LEFT OUTER JOIN、INNER JOIN)
    NPOI 读取Excel文件
    C# 截屏
    C# 程序运行进度显示Lable
    gridcontrol 添加行删除行
    C# 任务 数据加载不影响其他操作
    ChartControl ViewType.Pie3D 用法测试
  • 原文地址:https://www.cnblogs.com/maxiaowei0216/p/12518468.html
Copyright © 2011-2022 走看看