博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C/C++面向对象编程之继承
阅读量:4092 次
发布时间:2019-05-25

本文共 3065 字,大约阅读时间需要 10 分钟。

C/C++面向对象编程

目录:

C/C++面向对象编程之继承

1、什么是继承与派生

继承(Inheritance) 可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。

派生(Derive) 和继承是一个概念,只是站的角度不同。继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。
被继承的类称为父类或基类,继承的类称为子类或派生类。“子类”和“父类”通常放在一起称呼,“基类”和“派生类”通常放在一起称呼。
派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。
以下是两种典型的使用继承的场景:

  1. 当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。
  2. 当你需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员

2、继承时的对象内存模型

以C++为例:

没有继承时对象内存的分布情况。这时的内存模型很简单,成员变量和成员函数会分开存储:

  • 对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象);
  • 成员函数与对象内存分离,存储在代码区。

有继承关系时,派生类的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享。

请看下面的代码:

#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语言的内存模型。

3、C语言运用继承的思想

在C语言中,可以利用“结构在内存中的布局与结构的声明具有一致的顺序”这一事实实现继承。利用继承的思想,C语言很容易就能实现较为复杂的代码。

RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。
RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示:
在这里插入图片描述
从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。下图则显示了 RT-Thread 中各类内核对象的派生和继承关系:
在这里插入图片描述
通过这种内核对象的设计方式,RT-Thread 做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。
RT-Thread的内核架构请看下边的文章:

参考资料:

联系作者:

欢迎关注本人公众号:

在这里插入图片描述

转载地址:http://vdnii.baihongyu.com/

你可能感兴趣的文章
Flutter 布局控件完结篇
查看>>
Koa2初体验
查看>>
Koa 2 初体验(二)
查看>>
Koa2框架原理解析和实现
查看>>
vue源码系列文章good
查看>>
你不知道的Virtual DOM
查看>>
VUE面试题总结
查看>>
写好JavaScript条件语句的5条守则
查看>>
原生JS中DOM节点相关API合集
查看>>
【TINY4412】U-BOOT移植笔记:(7)SDRAM驱动
查看>>
【TINY4412】U-BOOT移植笔记:(12)BEEP驱动
查看>>
单链表的修改和删除
查看>>
C++的三个基本特征:封装、继承、多态
查看>>
C++虚函数的总结
查看>>
什么是URL地址?
查看>>
C++多态的实现方式总结
查看>>
学习C++需要注意的问题
查看>>
C++模板
查看>>
C++双冒号(::)的用法
查看>>
【Unity】封装SQLite管理类
查看>>