ASE作业3 深入理解Callback函数
深入理解Callback函数
学号:SA*****200
1. menu 中解耦合的分析
menu 小程序的基本框架是,首先定义一个独立于数据的链表linktable
,然后在 menu 程序中使用这个链表来存储和管理程序的指令和指令描述等信息,并通过回调函数的方式,来使得各个指令有自己的操作方式。其各个文件的作用如下:
linktableinternal.h:定义链表和链表节点
linktable.h:定义链表的各种接口
linktable.c:实现链表接口
menu.c:主程序,在这里调用链表接口创建一条带有数据的链表,来管理程序命令
下图是 menu 的一些关键代码
可以看出,首先定义链表节点 LinkTableNode
,注意链表节点是不关心数据的,它只定义了一个指向下一个链表节点的指针;然后是链表 LinkTable
,可以认为这是一个单独的节点,只负责管理链表的头尾位置和节点数,以及锁。然后 tLinkTableNode
以及 tLinkTable
就是用 typedef
起的别名罢了,为了标识这是一个类型。
链表提供的操作函数,其参数均为:tLinkTable
类型(用于指明操作哪个链表)以及 tLinkTableNode
类型(用于标识操作哪个链表节点),而 tLinkTableNode
类型是不包含数据的,也即是说,我定义的这个链表,只提供存粹的操作链表,不操作数据,那么也就是说,因此,什么数据类型都可以使用我这个链表。这就是一种解耦合:接口只提供接口的操作,
然后,menu.c
中定义了本程序所需要的数据结构——DataNode
。其定义中包括了,本程序需要用到的数据:命令,命令描述,命令操作等,以及一个 链表节点类型的 head
。然后调用初始化函数 InitMenuData
创建链表。该函数的参数是一个指向链表指针的指针。首先是创建链表,这一步是纯粹的链表操作;然后给 DataNode 分配内存并赋值,这一步是存粹的数据操作;然后,调用链表提供的 AddLinkTableNode
接口,把数据节点加入到链表中,这里,DataNode *
类型的参数,被强制转换为了 LinkTableNode *
类型,这就是不关心数据类型的链表,能插入各种数据的实现方法:插入进来的数据,都是被强制转换成统一的,由链表接口定义的类型插入进来的。
也就是说,使用上面提到的“抽象”链表,可以创建一个下面这样的带有数据的链表。数据就像是附着在链表节点上,而非属于接口中定义的链表本身。
2. 回调的概念
将函数作为参数,传入某一外部函数,以便该外部函数可以调用此函数来完成某些任务,这样的函数就称为回调函数(CallBack,call-then-back)
上图是一个示例,应用程序在调用某一库函数时,通过将函数地址作为参数传递的方式,指定了一个回调函数。库函数执行过程中,会调用这一函数,来完成应用程序的请求。一般回调函数都是和应用程序处于同一抽象层,这样就形成了一个:高层调用底层,底层再反过来调用高层的过程。
事实上,回调不仅可以用于在应用程序和库函数之间,把库函数换成别的中间函数也行。
3. 回调的优势
回调函数为应用提供了很大的灵活性、可拓展性。
一方面,同一个接口(即中间函数),根据起始函数传入的参数(即回调函数地址)不同,可以表现出不同的行为,这体现了多态的思想。
另一方面,采用这种方法,也满足了开闭原则,即你可以在不修改中间函数源代码的情况下,仅通过修改或者添加回调函数,便可以变更接口的行为。
4. 回调函数执行分析
menu中,通过传递参数一个函数 handler
做为参数,实现了同一段核心代码,对不同参数有不同表现的功能,且可以在不修改核心代码的前提下,添加新的指令。我们可以将这一思想提取出来,写一个更简单的例子,来对回调过程进行分析。
下面是一个回调函数执行的例子(meun.c
中执行过程与此相仿)
![]() |
![]() |
现有一个接口 getOddNum,可以给定任意整数,它返回一个奇数。至于得到奇数的方式,则由用户自定义,比如是采用 2K+1
还是 4k+1
的形式,由传入的回调函数确定。
main 函数以 doubleNum 为参数调用 getOddNum 时,会把其地址传入 rsi
寄存器:movl $_Z9doubleNumi, %esi
,然后就调用接口, call _Z9getOddNumiPFiiE
,开始接口的处理过程了。
在 getOddNum 函数中,分配栈帧后,会 call *%rsi
,也即调用 rsi
寄存器中的地址指向的函数,而 rsi 中存放的,正是在 main 函数中放入的 doubleNum 的入口地址。可见,确实是在 getOddNum 函数中调用的回调函数。
进入 doubleNum 函数后,它会把 rdi 寄存器中的参数加上自身后返回,返回到 getOddNum 函数中,然后 getOddNum 函数对 rax 寄存器中的返回值加一,把 rsp 指向 main 函数的栈顶,然后返回。此时,main 函数便完成了一次对接口 getOddNum 的调用,传入3,得到了奇数 7。
想以 4k+1
的方式得到奇数,也不需要改动接口 getOddNum 的代码,只需要添加一个回调函数 fourTimesNum
即可,调用的方式也不变,简单满足了开闭原则。