说明:该方法实现的是动态多态
先上代码
base.h文件
typedef struct base_fun base_fun_t;
typedef struct base_fun
{
void (*base_api)(base_fun_t* api, int t);
}base_fun_t;
typedef struct test22
{
base_fun_t base_fun;
int a;
int b;
}base_attr_t;
void output(base_fun_t* api, int t);
base_fun_t * base_arrr_init(base_attr_t* base_attr);
void base_fun_init(base_fun_t* base, void (*base_api)(base_fun_t*, int));
base.c文件
#include <stdio.h>
#include "base.h"
static void base_api(base_fun_t* base, int t);
//对外API实现
static void base_api(base_fun_t* base, int t)
{
base_attr_t* base_attr = (base_attr_t*)base;
if (t > base_attr->a)
{
printf("true!\r\n");
}
else
{
printf("false!\r\n");
}
}
//基类初始化
void base_fun_init(base_fun_t *base, void (*base_api)(base_fun_t *, int))
{
base->base_api = base_api;
}
//初始化
base_fun_t* base_arrr_init(base_attr_t* base_attr)
{
base_fun_init(&base_attr->base_fun, base_api);
base_attr->a = 14;
base_attr->b = 15;
return &(base_attr->base_fun);
}
//对外API
void output(base_fun_t* base, int t)
{
base->base_api(base, t);
}
mian.c文件
#include <stdio.h>
#include <stdlib.h>
#include "base.h"
#include "son.h"
//主函数
int main(void)
{
base_attr_t base_attr;
base_fun_t* base_fun = base_arrr_init(&base_attr);
output(base_fun, 16);
printf("test\r\n");
system("pause");
return 0;
}
输出如下:
分析:在主函数首先创建一个base_attr,然后类base_attr在初始化里给基类(嵌套结构体)初始化,把函数指针base_api传给了base_fun的base_api,再接着把基类指针返回给base_fun(这个很关键)
在output函数里,函数执行base_fun.base_api (base_fun_t* api, int t),此时把一个基类指针传了进去,接下来就是重点,为什么拿到基类强转为子类指针的base_attr->a的值能传进来呢。秘密就在于那个初始化函数,他初始化后返回一个base_attr_t base_attr的地址,没错,虽然他返回的是一个基类地址,但那同时也是子类的地址,因为结构体第一个成员的地址就是结构体的地址。接下来使用的基类就相当于使用只有部分成员的子类,因为结构体的地址是连续的(不考虑对齐),只要在使用期间基类指针没被改变,那基类指针一直等于子类指针,子类的成员属性也不会丢失,当基类指针强转回子类指针时,全部成员的访问权限就又回来了。
依照继承的这种属性,我们可以在给定API接口后,在基类的基础上添加新属性方法,以此在旧功能上添加新功能而不用重写整个程序。
在上一个的基础上添加新功能,代码如下:
son.h文件
#include "base.h"
typedef struct son
{
base_fun_t son_fun;
base_fun_t *son_attr;
}son_t;
base_fun_t* son_init(son_t* son, base_fun_t* base);
son.c文件
static void son_output(base_fun_t* base, int t);
//儿子,对外API的功能增加
static void son_output(base_fun_t *base, int t)
{
son_t* son = (son_t*)base;
printf("我是儿子\r\n");
output(son->son_attr, t);
}
base_fun_t* son_init(son_t *son, base_fun_t *base)
{
base_fun_init(&son->son_fun, son_output);
son->son_attr = base;
return &son->son_fun;
}
main.c文件
#include <stdio.h>
#include <stdlib.h>
#include "base.h"
#include "son.h"
//主函数
int main(void)
{
son_t son;
base_attr_t base_attr;
base_fun_t *base_attr_s = base_arrr_init(&base_attr);
base_fun_t* base_fun = son_init(&son, base_attr_s);
output(base_fun, 13);
printf("test\r\n");
system("pause");
return 0;
}
新增了这两个文件后,输出如下:
分析:理一下流程,首先是base_fun_t base_attr_s = base_arrr_init(&base_attr),该函数返回一个指向已初始化base_attr_t指针,只是类型是base_fun_t。然后该指针在son_init里传给了base_attr的son_attr,这就相当于初始化了base_attr的son_attr。在语句base_fun_t base_fun = son_init(&son, base_attr_s),传进去的子类son的son_fun被传递了功能函数son_output,这就是初始化了son的son_fun。接下来就是output(base_fun, 13),这里base_fun是指向son的指针,我们再看一下output函数:
void output(base_fun_t* base, int t)
{
base->base_api(base, t);
}
这里调用了base->base_api(base, t),这是指向哪个函数呢?没错,就是指向son里的son_fun,所以接下来执行的是void son_output(base_fun_t* base, int t),在函数里有这么一句son_t* son = (son_t*)base;,这是为什么呢,因为这时我们需要把它展开,把指针指向下一个fun,也就是son->son_attr->base_fun,这就回到我们的基础功能函数。这就像千层饼,我们一层一层地在基层功能上叠加新功能,一次又一次地进入output(base_fun_t* base, int t)函数,有点像递归。
使用这种方法容易搞混的是在,在新增功能时,传入外的API函数的指针要理清楚,一层一层不能有错,每次传入的都是基类指针但该指针指向其子类。
通过这一番折腾,我们在基本功能上增加了一个打印功能,没有修改原有的函数,只是在原有函数上扩展子类,添加新功能函数。用传统的面向过程开发,在模块里当有功能改动时往往会有逻辑业务层、底层的改动,很容易导致大面积的函数重写,而用这种面向对象的方法,当有新功能增加时,我们只需要扩展子类,在基本功能的基础上插入新的功能,不需要修改底层,反过来变成依赖基本的API接口。
参考资料:《嵌入式软件设计方法》0.3.12