纯C语言实现简单封装继承机制

来源:互联网 发布:mysql 索引 编辑:程序博客网 时间:2024/06/09 22:47

0 继承是OO设计的基础

继承是OO设计中的基本部分,也是实现多态的基础,C++,C#,Objective-C,Java,PHP,JavaScript等为OO而设计的语言,其语言本身对实现继承提供了直接支持。而遵循C/Unix设计哲学的语言,从不限定编程风格,而且提供了实现OO的基本支持。下面我们就来看看如何用C语言实现继承。

1 内存布局层面上继承的含义

如今几乎所有程序员都知道继承的抽象含义,对于被用烂了的猫狗继承动物的例子也耳熟能详。在此,我们抛开抽象世界,深入到继承的具体实现上。当然不同的语言对继承的实现机制并不完全相同,但是了解其中一种典型的实现细节对于理解继承是非常有好处的。这里我们以C++为例进行说明。

class B{    int x;    int y;    int z;};class C : B{    float f;    char s[10];};

上述代码表示子类C继承了父类B,下面是类C的一个实例(对象)的内存布局。
这里写图片描述

C对象有两部分组成,红色区域是继承自B的部分,蓝色区域是自身特有的。这样一来,红色部分完全可以当成是一个B类对象。

2 利用结构体实现继承的两种方法

2.1 父类对象作为子类的成员

理解了继承的内存布局原理之后,用C来实现继承就非常容易了。最容易想到的方法如下:

struct B{    int x;    int y;    int z;};struct C{    struct B objB;    float f;    char s[10];};

上述代码通过在C中包含一个B类型的成员来实现继承,此方法非常直接,但使用起来有一些不太方便。

    struct C objC;    objC.objB.x = 10;    ((struct B*)&objC)->x = 10;

要想访问父类的成员x,有两种方法,一种是objC.objB.x;另一种是((struct B*)&objC)->x = 10。这两种方式都看起来不够直接。而在子类方法中访问父类成员是非常频繁的。

void c_member_method(struct C* pObjC){    pObjC->objB.x = 20; /* 访问父类成员 */    pObjC->f = 0.23f; /* 访问自身成员 */}

第一种方法,感觉更像是OB风格,而不是OO。
第二种方法,必须进行强制类型转换,感觉语法上不够美观。

2.2 子类包含所有的父类成员

struct C{    int x;    int y;    int z;    float f;    char s[10];};

把所有的父类成员原样作为子类的成员。这样子类对象访问继承来的成员就非常直接了。

void c_member_method(struct C* pObjC){    pObjC->x = 20; /* 访问父类成员 */    pObjC->f = 0.23f; /* 访问自身成员 */}void main(){    struct C objC;    objC.x = 10;}

看起来很好,实际上在工程上会存在一个很大的问题:难以维护!例如,每当创建一个子类,必须原样书写所有的父类成员,当父类定义变动时,子类需要做出同样的修改。一旦父类稍具规模,维护这种继承关系将是一场噩梦!

那么如何解决的?
方法是现成的,那就是利用C语言的预处理宏定义#define. 如下所示:

#define B_STRUCT \    int x; \    int y; \    int zstruct B{    B_STRUCT;};struct C{    B_STRUCT;    float f;    char s[10];};

当继承层级更深时,例如 C继承B,D继承C,可以照搬此方法。

#define B_STRUCT \    int x; \    int y; \    int zstruct B{    B_STRUCT;};#define C_STRUCT \    B_STRUCT; \    float f; \    char s[10]struct C{    C_STRUCT;};#define D_STRUCT \    C_STRUCT; \    double dstruct D{    D_STRUCT;};

通过宏定义,可以很容易实现和维护这种继承关系。

3 方法(成员函数)的封装与继承

C语言中没有成员函数的概念,语言本身也不支持。使用C语言实现真正的成员函数几乎是不可能的,除非嵌入汇编语言。与其使用汇编语言,还不如直接使用C++呢。所以,我们不追求形式上的成员函数,只实现意义上的成员函数–(对给定类型对象进行操作)的函数,并使用带结构名前缀的函数名加以命名之。我们还是以上面的例子进行说明。

typedef struct B B;static void b_member_function(B* pobjB) /* 类B的成员函数 */{}typedef struct C C;static void c_member_function(C* pobjC) /* 类C的成员函数 */{}

对成员函数的调用,有两种情形:(1)外部代码调用成员函数;(2)子类成员函数中调用父类的成员函数;

static void c_member_function(C* pobjC){    b_member_function((B*)pobjC); /* 子类成员函数内部调用父类成员函数 */}void main(){    C* pObjC = malloc(sizeof(C));    b_member_function((B*)pObjC);  /* 外部代码调用成员函数 */    free(pObjC);}

这两种情况都需要对实参进行强制类型转换为父类型。C编译器对类型继承关系一无所知,无法从语法上对继承进行自动支持,所以只能手动强制类型转换了。

有些人喜欢更进一步模拟成员函数,把所有成员函数的地址作为指针类型的成员变量存储到结构体内部。如下:

#define B_STRUCT \    int x; \    int y; \    int z; \    void (*pb_member_function1)(B*); \    void (*pb_member_function2)(B*, int arg)struct B{    B_STRUCT;   };/* 初始化B对象的同时初始化 */B* b = malloc(sizeof(B));b->pb_member_function1 = b_member_function1;b->pb_member_function2 = b_member_function2;/* 调用 */b->b_member_function1(b);

这样形式上更加接近“成员函数”,但同时也带来了额外的内存开销和代码量。为了减小内存消耗,有人提出不再在对象中完全存放所有成员函数指针,而是只存放一个指向成员函数地址列表的指针。毕竟,同一类型的所有实例(对象)共享相同的一组成员函数。

/* B类型的成员方法表 */const struct B_MethodTable{    void (*pb_member_function1)(B*);     void (*pb_member_function2)(B*, int arg);}b_method_table{    b_member_function1,    b_member_function2,};#define B_STRUCT \    int x; \    int y; \    int z; \    struct B_MethodTable * pMethodTable;struct B{    B_STRUCT;   };/* 初始化B对象的同时初始化 */B* b = malloc(sizeof(B));b->pMethodTable = &b_method_table;/* 调用 */b->pMethodTable->pb_member_function1(b);

这样在一定程度上减小了内存占用量和代码量,但是队成员函数的调用写法却变得非常繁琐不自然。

4 做到何种程度?

使用C语言做OO开发时,要掌握好一个度。不要过分追求对OO语言C++的模拟,完全模拟C++的话,还不如干脆直接使用C++。

  • 有些语言特性无法模拟,如C++中private,protected等访问限定符,成员函数的this指针。更应该注重意义上的模拟,通过一些命名规则和约定来达到OO。
  • 永远不要违背C语言的设计哲学:程序员控制一切,直接简明。

这个度的把握需要根据具体的项目规模和需求,是实践中摸索出来的,无法给出理论上的最优值。

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 转账户名写错了怎么办 工资卡开户写错公司名称怎么办 商标跟别人重了怎么办 不受信任的应用打不开怎么办 oppo手机安全证书过期怎么办 网站安全证书有问题怎么办 公章圆圈摔坏了怎么办 高风险办税人员怎么办 公司因担保被起诉怎么办 借公司钱被起诉怎么办 qq群管理员满了怎么办 微信公众号搜索不到怎么办 微信名字改不了怎么办 微信号第一次限制登录怎么办 微信第一次限制登录怎么办 老板不回我微信怎么办 微信号换手机号了怎么办 电话被对方拉黑怎么办 微信被好友拉黑了怎么办 微信收藏删了怎么办 如果忘记支付宝登录手机号码怎么办 支付宝登录密码忘记了怎么办 支付宝账户名忘记了怎么办 搜索qq号搜不到怎么办 微信号手机号码换了怎么办 起诉以后没有被告人住址怎么办 农村老人走丢了怎么办 读在职博士工作单位不支持怎么办 两证合一后国税怎么办 杭州的发票丢了怎么办 小车登记证丢了怎么办 个体户地税逾期未申报怎么办 公司社保本丢了怎么办 社保红本子掉了怎么办 三证合一后逾期怎么办 个体执照没办国税地税怎么办 丰巢APP注册没工牌号怎么办 农业银行k宝证书过期怎么办 个体户网上申报税没定期怎么办 遇到不给开票的商户怎么办 奶茶店电脑下单怎么办