纯C语言实现简单封装继承机制
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来实现继承就很easy了。最easy想到的方法例如以下:
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;
}
看起来很好,实际上在project上会存在一个很大的问题:难以维护。比如,每当创建一个子类。必须原样书写全部的父类成员,当父类定义变动时,子类须要做出同样的改动。一旦父类稍具规模,维护这样的继承关系将是一场噩梦。
那么怎样解决的?
方法是现成的,那就是利用C语言的预处理宏定义#define. 例如以下所看到的:
#define B_STRUCT \
int x; \
int y; \
int z
struct B
{
B_STRUCT;
};
struct C
{
B_STRUCT;
float f;
char s[10];
};
当继承层级更深时。比如 C继承B,D继承C,能够照搬此方法。
#define B_STRUCT \
int x; \
int y; \
int z
struct B
{
B_STRUCT;
};
#define C_STRUCT \
B_STRUCT; \
float f; \
char s[10]
struct C
{
C_STRUCT;
};
#define D_STRUCT \
C_STRUCT; \
double d
struct D
{
D_STRUCT;
};
通过宏定义。能够很easy实现和维护这样的继承关系。
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语言的设计哲学:程序猿控制一切,直接简明。
这个度的把握须要依据详细的项目规模和需求。是实践中摸索出来的。无法给出理论上的最优值。
纯C语言实现简单封装继承机制的更多相关文章
- javascript继承机制的设计思想(ryf)
我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...
- C++学习笔记 封装 继承 多态 重写 重载 重定义
C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...
- 【JavaScript】重温Javascript继承机制
上段时间,团队内部有过好几次给力的分享,这里对西风师傅分享的继承机制稍作整理一下,适当加了些口语化的描述,留作备案. 一.讲个故事吧 澄清在先,Java和Javascript是雷锋和雷峰塔的关系.Ja ...
- 转:Javascript继承机制的设计思想
我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...
- javascipt继承机制(from阮一峰)
Javascript继承机制的设计思想 我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类" ...
- C++三大特性 封装 继承 多态
C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...
- Javascript继承机制的设计思想
转自:http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.htm ...
- Javascript prototype 及 继承机制的设计思想
我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...
- 带你理解【JavaScript】中的继承机制
前文 总所周知,继承是所有OO语言中都拥有的一个共性.在JavaScript中,它的继承机制与其他OO语言有着很大的不同,尽管ES6为我们提供了像面向对象继承一样的语法糖,但是其底层依然是构造函数,所 ...
随机推荐
- idea java 注释模板配置
在网上找了好久,好多的文章都有一个共同的病点就是“@param注释当有多个参数时候,全部放在了一行里面”,非常不友好. 以下是我整理好的,完全按照eclipse的注释风格. !!!先看最后实现的效果图 ...
- The following signatures couldn't be verified because the public key is not available: NO_PUBKEY XXXX
reference apt-key adv --recv-keys --keyserver keyserver.ubuntu.com XXXXXX apt-get update
- TWaver可视化编辑器的前世今生(四)电力 云计算 数据中心
插播一则广告(长期有效) TWaver需要在武汉招JavaScript工程师若干 要求:对前端技术(JavasScript.HTML.CSS),对可视化技术(Canvas.WebGL)有浓厚的兴趣基础 ...
- JS简单实现防抖和节流
一.什么是防抖和节流 Ps: 比如搜索框,用户在输入的时候使用change事件去调用搜索,如果用户每一次输入都去搜索的话,那得消耗多大的服务器资源,即使你的服务器资源很强大,也不带这么玩的. 1. 防 ...
- 洛谷—— P1268 树的重量
P1268 树的重量 构造类题目,看不出个所以然来... emmm,只好看题解: 只有两个点,那一条路径就是$ans$ 考虑三个点,那么$3$这个点相对于树上的路径(已经加入树上的边的距离) 为:$( ...
- python中的参数、全局变量及局部变量
1.位置参数.关键字参数.默认参数的使用 位置参数.关键字参数 def test(x,y,z): print(x) print(y) print(z) test(1,2,3) #位置参数,必须一一对应 ...
- Django框架基础知识04-模板标签与模板继承
1.常用的模板标签 -作用是什么? -常用标签 总结:语法 {% tag %} {% endtag %} {% tag 参数 参数 %} 2.模板的继承与引用 -引用 include标签 -继承 ex ...
- solr 时区问题
本人使用solr版本5.0.0,使用jetty启动 solr默认UTC时区,与我们相差八小时,按照网络上资料修改 C:\Users\hp\Desktop\solr-5.0.0\bin 下的solr.i ...
- git clone, push, pull, fetch 的用法
Git是目前最流行的版本管理系统,学会Git几乎成了开发者的必备技能. Git有很多优势,其中之一就是远程操作非常简便.本文详细介绍5个Git命令,它们的概念和用法,理解了这些内容,你就会完全掌握Gi ...
- Codevs 1497 取余运算== 洛谷P 1226
时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 输入b,p,k的值,编程计算bp mod k的值.其中的b,p,k*k ...