继承
- 概念
- 使用方法
- 继承方式
- 子类的构造与析构
- 继承的成员
- 继承成员在子类对象里的存放顺序
- 成员变量
- 普通成员变量
- 静态成员变量
- 成员函数
- 普通成员函数
- 重定义(隐藏)
- 静态成员函数
- 友元函数
- 单继承与多继承
- 概念
- 赋值转换(切片)
- 多继承带来的菱形继承
- 菱形虚拟继承
- 虚继承
- 概念
- 使用方法
- 虚继承中子类对象的内容
- 到虚继承的变量的偏移量
- 虚基表
- 使用指针间接寻址,而不是直接把偏移量放在对象里的原因
概念
- 在日常开发中,我们不免会碰到这样的情况:要大量设计有相同功能的多个类,但是每个类又有一些功能是特有的,设计时就会显得很冗余
- 为了改善这种情况,C++引入了继承的概念,继承就是指继承的类可以使用被继承的类中的指定功能
使用方法
派生类 继承方式
class Student : public Person基类/父类
继承方式
- 简单总结继承方式
- 父类中,如果有私有成员:子类可以继承私有成员,但是私有成员在子类中是不可使用的,类似于隐藏
- 子类选择的继承方式,就是子类能继承到的最大级别:如果基类中有
public
成员而子类选择了protected
继承方式,那么子类继承的public
成员都会降级为protected
子类的构造与析构
- 子类的构造都是先调用基类的构造函数或拷贝函数再调用自身的拷贝/构造函数(如果基类没有默认构造函数,就要在子类的初始化列表里显示调用构造函数)
- 先析构子类再析构父类(这里的析构函数有用到重写,具体在多态章节会了解)
继承的成员
继承成员在子类对象里的存放顺序
class Student : public Person, public Man
- 简单总结:先到先得,手慢靠后
成员变量
普通成员变量
- 正常继承给子类,是属于子类的内容,可以正常使用
静态成员变量
- 静态成员变量属于:可以继承也不可以继承的范畴,因为静态成员变量可在整个继承体系中共同使用,任何一个类对象改变了静态成员变量的值,都会影响下一个使用该变量的类对象
成员函数
普通成员函数
- 子类继承后如果没有重写或重定义,则子类对象调用函数时,调用的是同一个函数
- 如果重写了,就根据调用的对象来调用对应的函数(多态)
- 如果重定义了,且调用了某一函数,就会在子类中直接使用重定义后的函数,若是重定义后的函数的参数列表不符合传入的参数,且类中没有重载,则不会跳到基类继续找匹配的函数,而是直接报错
重定义(隐藏)
- 重定义的规则:
- 发生的场景在父类与子类之间
- 仅是函数名相同,且不构成重写
静态成员函数
- 如同静态成员变量一样,继承体系中的类都可以使用,共享使用权
友元函数
- 友元函数不能被继承,友元函数本质上不是类的成员,只是在类里声明能够使用类里成员的一个函数而言
单继承与多继承
概念
-
单继承:一个子类只有一个直接父类
-
多继承:一个子类有两个或两个以上直接父类
赋值转换(切片)
- 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。
- 有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
- 基类对象不能赋值给派生类对象。
- 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的 [多态]
多继承带来的菱形继承
菱形虚拟继承
- 如果真的有人写出了菱形继承,那么不难发现它的缺点:数据冗余性和二义性
- 数据冗余性:最后的子类存放了两个Person父类的内容
- 二义性:两个Person父类的内容名称完全一样,如果调用了继承的同名对象,则无法判断是要调用哪个
为了解决这个问题,C++创建了虚继承
虚继承
概念
- 虚继承:为解决数据冗余性和二义性,让某一个父类的成员变量,每次被继承时都是直接继承父类的成员变量,而不是直接父类里父类的成员变量
使用方法
class A
{
public:int a = 10;
};
class B : virtual public A
{
public:int b = 6;
};
class C : virtual public A
{
public:int c = 3;
};class D : virtual public B, public C
{
public:int d = 0;
};int main()
{A a;B b;b.a = 100;C c;c.a = 1000;D d;return 0;
}
虚继承中子类对象的内容
- 根据上面的图,虚继承中子类对象的内容有一点很可疑:B里面没有A的内容,那除了B本身的内容,还会有其他的内容吗
- 答案是有的,并且还是个指针,指针指向的是:到虚继承获得的变量的偏移量大小
到虚继承的变量的偏移量
- 多个对象时,共用一张虚基表
事实上,D里的第一个存放的内容应该是与A的偏移量才是,这里画图失误
虚基表
- 虚基表里存放的是:到虚继承的成员的相对位置
使用指针间接寻址,而不是直接把偏移量放在对象里的原因
- 如果直接把偏移量放在对象里:若D被继承,由于B和C无法被修改,会导致偏移量也无法修改。
因此通过指针间接寻找偏移量是较优的选择