目录
01.概念
02.虚拟继承
原理
03.继承和组合
01.概念
单继承:
一个子类只有一个父类时,称这种继承关系为单继承。
多继承:
一个子类同时有两个及以上的父类时,称这种继承关系为多继承。
菱形继承:
菱形继承是多继承的一种特殊形式。
#include<iostream>
using namespace std;
class Person
{
public:string _name; // 姓名
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name = "peter";// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
在上述结构中,类 Assistant
通过类 teacher
和类 student
两次继承了类 person
,导致 Assistant
具有两份 person
的副本。这会带来以下问题:
- 内存浪费:
Assistant
有两个person
的副本。 - 二义性:访问
person
中的成员变量或方法时,编译器无法确定是通过teacher
还是student
继承的person
。
02.虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余问题,如上面的继承关系,在student和teacher的继承person时使用虚拟继承,即可解决问题。
class Person
{
public:string _name; // 姓名
};
class Student : virtual public Person
{
protected:int _num; //学号
};
class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{Assistant a;a._name = "peter";
}
此时就不存在了对_name访问不明确的问题,因为虚拟继承保证在整个继承层次中只存在一份基类的实例。
原理
我们用一个简化的菱形继承体系,再借助内存窗口观察对象成员的模型。
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
下面是菱形继承的内存对象成员模型:这里可以看到数据冗余:
下面是菱形虚拟继承的内存对象成员模型:
这里可以分析出D对象中将A放到了最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
03.继承和组合
继承是一种“is-a”关系,表示一个类是另一个类的特殊化。通过继承,子类可以获得父类的属性和方法。
特点
- 代码复用:子类继承了父类的属性和方法,减少了代码重复。
- 层次结构:形成类的层次结构,表示通用和特定的关系。
- 多态性:通过继承,可以实现多态,即使用父类引用指向子类对象。
组合是一种“has-a”关系,表示一个类包含另一个类作为其成员。组合通常用于表示类之间的部分-整体关系。
特点
- 灵活性:组合比继承更加灵活,可以动态地改变组合对象的行为。
- 低耦合度:类之间的耦合度较低,有助于维护和扩展代码。
- 封装性:通过组合,可以将类的实现细节封装起来,隐藏复杂性。
class Engine {
public:void start() {cout << "Engine started" << endl;}
};class Car {
private:Engine engine; // Car 包含一个 Engine 对象public:void start() {engine.start(); // 使用 Engine 的方法cout << "Car started" << endl;}
};int main() {Car myCar;myCar.start(); // 组合了 Engine 对象return 0;
}
在这个例子中,Car
包含一个 Engine
对象,因此 Car
“拥有” 一个 Engine
。
继承 vs 组合
-
继承:
- 优点:代码复用、层次结构、多态性。
- 缺点:强耦合,子类依赖于父类的实现,修改父类可能会影响到所有子类。
-
组合:
- 优点:灵活性高,低耦合度,易于维护和扩展。
- 缺点:可能需要编写更多的代码来包装组合对象的功能。
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有 些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用 继承,可以用组合,就用组合。
以上就是菱形继承相关知识的整理了,欢迎在评论区留言,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉