meun程序设计方法分析

meun程序设计方法分析

学号:SA*****200

menu 中解耦合的分析

​ menu 小程序的基本框架是,首先定义一个独立于数据的链表linktable,然后在 menu 程序中使用这个链表来存储和管理程序的指令和指令描述等信息,并通过回调函数的方式,来使得各个指令有自己的操作方式。其各个文件的作用如下:

  1. linktableinternal.h:定义链表和链表节点

  2. linktable.h:定义链表的各种接口

  3. linktable.c:实现链表接口

  4. menu.c:主程序,在这里调用链表接口创建一条带有数据的链表,来管理程序命令

下图是 menu 的一些关键代码

image-20230403122914392

​ 可以看出,首先定义链表节点 LinkTableNode,注意链表节点是不关心数据的,它只定义了一个指向下一个链表节点的指针;然后是链表 LinkTable,可以认为这是一个单独的节点,只负责管理链表的头尾位置和节点数,以及锁。然后 tLinkTableNode 以及 tLinkTable 就是用 typedef 起的别名罢了,为了标识这是一个类型。

​ 链表提供的操作函数,其参数均为:tLinkTable 类型(用于指明操作哪个链表)以及 tLinkTableNode 类型(用于标识操作哪个链表节点),而 tLinkTableNode 类型是不包含数据的,也即是说,我定义的这个链表,只提供存粹的操作链表,不操作数据,那么也就是说,因此,什么数据类型都可以使用我这个链表。这就是一种解耦合:接口只提供接口的操作,

​ 然后,menu.c 中定义了本程序所需要的数据结构——DataNode。其定义中包括了,本程序需要用到的数据:命令,命令描述,命令操作等,以及一个 链表节点类型的 head。然后调用初始化函数 InitMenuData 创建链表。该函数的参数是一个指向链表指针的指针。首先是创建链表,这一步是纯粹的链表操作;然后给 DataNode 分配内存并赋值,这一步是存粹的数据操作;然后,调用链表提供的 AddLinkTableNode接口,把数据节点加入到链表中,这里,DataNode * 类型的参数,被强制转换为了 LinkTableNode * 类型,这就是不关心数据类型的链表,能插入各种数据的实现方法:插入进来的数据,都是被强制转换成统一的,由链表接口定义的类型插入进来的。

​ 也就是说,使用上面提到的“抽象”链表,可以创建一个下面这样的带有数据的链表。数据就像是附着在链表节点上,而非属于接口中定义的链表本身。

image-20230403131348122

回调的概念

​ 将函数作为参数,传入某一外部函数,以便该外部函数可以调用此函数来完成某些任务,这样的函数就称为回调函数(CallBack,call-then-back)

回调函数

​ 上图是一个示例,应用程序在调用某一库函数时,通过将函数地址作为参数传递的方式,指定了一个回调函数。库函数执行过程中,会调用这一函数,来完成应用程序的请求。一般回调函数都是和应用程序处于同一抽象层,这样就形成了一个:高层调用底层,底层再反过来调用高层的过程。

​ 事实上,回调不仅可以用于在应用程序和库函数之间,把库函数换成别的中间函数也行。

回调的优势

​ 回调函数为应用提供了很大的灵活性、可拓展性。

​ 一方面,同一个接口(即中间函数),根据起始函数传入的参数(即回调函数地址)不同,可以表现出不同的行为,这体现了多态的思想。

​ 另一方面,采用这种方法,也满足了开闭原则,即你可以在不修改中间函数源代码的情况下,仅通过修改或者添加回调函数,便可以变更接口的行为。

回调函数执行分析

​ 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 即可,调用的方式也不变,简单满足了开闭原则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值