文章目录
- 一.面向对象的三大特性之一:继承
- 继承中的一些其他语法规定
- 二.父类和子类间的对象赋值转换
- 三.继承体系与类的默认成员函数--思维导图
- 四.继承对象的内存模型
- 五.继承的缺陷--菱形继承问题
- 对于继承的认知
一.面向对象的三大特性之一:继承
- 继承是类域层面上的层次划分,其语法设计的目的是为了提高代码的复用性(继承是对类整体的复用)同时令项目设计的层次划分更加清晰,现有如下场景:比如现在要设计两个类:Student类和Teacher类,然而这两个类包含了一些相同的成员(比如_name,_age以及一些相同的接口等等),于是我们便可以抽象出一个Person类(类中包含Student类和Teacher类中相同的成员),然后令Student类和Teacher类去继承Person类形成如下的类域层次:
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
private:string _name = "peter";int _age = 10;
};class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};
- 继承关系确定后,父类中的所有成员都会被继承到子类中.
- 父类private成员在子类中是不能被访问的(但仍然存在于子类中,即实例化时会占用内存空间)
- 继承方式分类与被继承的类成员在子类中的访问限定属性:
- 可以发现,父类的成员被继承后,其访问限定属性变化遵循"权限只缩小不扩大"的规律
- 在继承体系中,子类和父类都有各自独立的作用域,当子类和父类中出现同名成员时,子类的成员会屏蔽对父类同名成员的直接访问(此时,若想访问父类中的同名成员,需要指定类域进行访问):
- 需要注意的是,如果父类和子类中出现重名的成员函数,则依然会形成上述的屏蔽关系而不会构成函数重载
继承中的一些其他语法规定
- 友元关系不能继承,即父类的友元不能成为子类的友元
- 如果在父类中定义了
stactic
静态成员,则在整个继承体系中只存在一个该成员的实例
二.父类和子类间的对象赋值转换
- 子类对象可以赋值给父类的对象或者父类的引用.
- 子类对象的指针可以赋值给父类对象的指针.
- 上述赋值过程相当于将子类中的父类部分切割出来赋值给父类的对象(或引用或指针),可形象化如下:
- 该语法在继承体系的类对象间的拷贝赋值会用到
- 父类对象不能赋值给子类对象(或引用),父类对象的指针不能赋值给子类对象的指针.
三.继承体系与类的默认成员函数–思维导图
四.继承对象的内存模型
- Derive类对象的内存模型:
五.继承的缺陷–菱形继承问题
- 菱形继承关系图:
- 类代码:
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 ;
};
- 菱形继承会导致同名成员变量重复在类中出现,造成二义性和内存空间浪费的问题,上面的继承关系的内存模型:
- 为了解决菱形继承中数据冗余和二义性的问题,C++设计了虚继承的语法来弥补菱形继承的缺陷:菱形继承关系中,在含有重复子类的中间子类的继承方式前加上
virtual
关键字:
- 经过
virtual
修饰后,Assistant类的内存模型会发生变化:
- 在实际工程中要避免设计出菱形继承的架构(一个子类继承多个父类的情况也要尽量避免),这是一个语法缺陷
对于继承的认知
- 相对于直接将一个类作为另一个类的成员变量,继承一定程度上破坏了类之间的封装性,违背了软件工程架构设计思想中高内聚低耦合的原则(子类和父类间的依赖关系很强,耦合度高),因此能将一个类直接作为另一个类的成员变量时,优先考虑适配器形式的复用方式,其次再考虑使用继承语法,只有在需要引入多态的时候,继承才是必须的