本文共 3065 字,大约阅读时间需要 10 分钟。
继承(Inheritance) 可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。
派生(Derive) 和继承是一个概念,只是站的角度不同。继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。 被继承的类称为父类或基类,继承的类称为子类或派生类。“子类”和“父类”通常放在一起称呼,“基类”和“派生类”通常放在一起称呼。 派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。 以下是两种典型的使用继承的场景:以C++为例:
没有继承时对象内存的分布情况。这时的内存模型很简单,成员变量和成员函数会分开存储:有继承关系时,派生类的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享。
请看下面的代码:#include//基类Aclass A{ public: A(int a, int b);public: void display();protected: int m_a; int m_b;};A::A(int a, int b): m_a(a), m_b(b){ }void A::display(){ printf("m_a=%d, m_b=%d\n", m_a, m_b);}//派生类Bclass B: public A{ public: B(int a, int b, int c); void display();private: int m_c;};B::B(int a, int b, int c): A(a, b), m_c(c){ }void B::display(){ printf("m_a=%d, m_b=%d, m_c=%d\n", m_a, m_b, m_c);}int main(){ A obj_a(99, 10); B obj_b(84, 23, 95); obj_a.display(); obj_b.display(); obj_b.A::display();//访问基类的同名函数 return 0;}/*输出:m_a=99, m_b=10m_a=84, m_b=23, m_c=95m_a=84, m_b=23*/
注意到派生类中B的成员和基类A中的成员有重名函数display(),这在C++里叫名字遮蔽,所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。
派生类的对象如果访问一个名字在派生类的成员内无法找到,编译器会智能的继续到基类作用域中查找该名字的定义;如果派生类的对象想访问基类的同名名字,需要加上类名和域解析符。 内存模型: obj_a 是基类对象,obj_b 是派生类对象。假设 obj_a 的起始地址为 0X1000,那么它的内存分布如下图所示: 假设 obj_b 的起始地址为 0X1100,那么它的内存分布如下图所示: 在派生类的对象模型中,会包含所有基类的成员变量。这种设计方案的优点是访问效率高,能够在派生类对象中直接访问基类变量,无需经过好几层间接计算;所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享。 以C语言为例: 在C语言的世界里里结构体不能包含函数体,每个功能函数的名字必须不同,所以自然没有像C++那样函数重载的功能和继承时的名字遮蔽问题。C语言里结构体只能包含变量,在派生类的对象模型中,会包含所有基类的成员变量。 C语言没有C++那样智能,C语言如果想访问基类成员,必须加上基类的对象名。 请看下面的代码:#include//基类Astruct A{ int m_a; int m_b; void (*display)(struct A *a);};void a_display(struct A *a){ printf("m_a=%d, m_b=%d\n", a->m_a, a->m_b);}//派生类Bstruct B{ struct A parent; int m_c; void (*display)(struct B *b);};void b_display(struct B *b){ printf("m_a=%d, m_b=%d, m_c=%d\n", b->parent.m_a, b->parent.m_b, b->m_c);}int main(){ struct A obj_a={ 99, 10,a_display}; struct B obj_b={ 99, 10, a_display,95,b_display}; obj_a.display(&obj_a); obj_b.display(&obj_b); obj_b.parent.display(&obj_a); return 0;}/*输出:m_a=99, m_b=10m_a=99, m_b=10, m_c=95m_a=99, m_b=10*/
C语言的内存模型比较简单,去除掉C++的函数体,就是C语言的内存模型。
在C语言中,可以利用“结构在内存中的布局与结构的声明具有一致的顺序”这一事实实现继承。利用继承的思想,C语言很容易就能实现较为复杂的代码。
RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。 RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示: 从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。下图则显示了 RT-Thread 中各类内核对象的派生和继承关系: 通过这种内核对象的设计方式,RT-Thread 做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。 RT-Thread的内核架构请看下边的文章:参考资料:
联系作者:
欢迎关注本人公众号:转载地址:http://vdnii.baihongyu.com/