C语言面向对象编程学习笔记

说明:该方法实现的是动态多态

先上代码

						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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值