继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
总结:
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" <<i<<endl;
}
};
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员*
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在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 ; // 主修课程
};
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
组合的构成:即在一个类里面定义另外一个类的对象
// Tire和Car构成has-a的关系
class Tire{
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t; // 轮胎
};
(1)菱形继承是指在类的继承关系中,存在两个或更多个直接或间接的基类,它们之间形成了一个类似菱形的结构。 (2)菱形继承带来的主要问题是数据冗余和二义性
(1)菱形虚拟继承是一种特殊的继承方式,用于解决菱形继承中的数据冗余和二义性问题。 (2)菱形继承是指一个类同时从两个直接或间接的基类继承,而这些基类又共同继承自同一个基类,形成了一个菱形结构。这种情况下,可能会导致数据冗余和访问不明确(二义性)的问题。 (3)菱形虚拟继承通过在基类声明中使用virtual关键字来解决这些问题。当在基类中使用虚拟继承时,派生类中只会存储一份基类的数据成员,通过虚基表(Vtable)和虚基表指针来管理这些共享数据,从而避免了数据冗余。虚基表包含了虚基类的偏移量信息,通过这些信息可以正确地访问到共享的数据成员,解决了二义性问题
使用场景方面: (2)继承适用于以下情况:
当两个类具有明显的“is-a”关系时,即一个类是另一个类的具体化或特例时,使用继承。例如,如果有一个“动物”类,而“狗”是动物的一种,那么狗类应该继承自动物类。 当一个类需要使用另一个类的所有方法或属性时,继承也是一个好的选择。例如,如果一个类需要使用另一个类中的大量属性和方法,通过继承可以很方便地获取这些功能。
(3)组合适用于以下情况:
当两个类之间存在“has-a”关系时,即一个类作为另一个类的组件时,使用组合。例如,如果一个“汽车”类需要包含一个“发动机”对象,那么汽车类可以将发动机作为自己的成员变量。 当需要保证类的安全性,不破坏封装时,使用组合。组合可以让一个类作为另一个类的组件,而不必暴露其全部接口,从而保持了较高的封装性。