C++ 继承相关概念详解,私有、保护、虚继承、菱形继承等
在阅读本文之前,回忆构造函数的几种形式,继承的几种形式,虚函数是什么,什么是多态,隐藏和重写的区别是什么?
1. 子类的构造
📌
1、子类的构造在执行它的构造函数前会根据继承表的顺序执行父类的构造函数。
默认执行父类的无参构造
显式调用有参构造,在子类的构造函数后,初始化列表中显式调用父类的有参构造函数
2、子类在它的析构函数执行完后,会根据继承表的顺序逆序执行父类的析构函数。
注意:父类的指针可以指向子类的对象,当通过父类指针释放对象时,只会调用父类的析构函数,这种析构方式有可能造成内存泄露。
所以最好把父类的析构函数设置为虚函数,这样父类指针可以调用子类的析构函数释放内存。
3、当使用子类对象来初始化新的子类对象时,会自动调用缺省的拷贝构造函数,并且会先调用父类的缺省的拷贝构造函数
如果子类中实现了拷贝构造需要显示调用父类的拷贝构造,否则就会调用无参构造
2. 私有继承、保护继承
📌
1、私有继承  
使用 private 方式继承父类,公开的成员在子类中变成私有的,其他的不变量,防止父类的成员扩散2、保护继承
使用 protected 方式继承父类,公开的成员在子类中成保护的,其他的不变量,防止父类的成员扩散子类以私有或者保护方式继承父类,会禁止向上造型(子类的指针或引用不能隐式转化成父类的指针或引用,要想实现多态只能以公开方式继承父类)
3. 多重继承、钻石继承、虚继承
📌
1、多重载继承
在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并按照顺序表中的调用父类的构造函数
按照从低到高的地址顺序排列父类,子类中会标记每个父类存储位置。
当子类指针转换成父类的隐式指针时候,编译器会自动计算父类中的内存所在子类中的位置,地址会自动偏移计算2、名字冲突
如果父类中有同名的成员,可以正常继承,但如果直接使用会造成歧义,需要 类名::成员名 进行访问。3、钻石继承 (菱形继承)
假如有一个类A,类B继承类A,类C也继承类A,然后类D继承类B和类C
一个子类继承多个父类,这多个父类有一个共同的祖先,这种继承叫钻石继承
注意:钻石继承不会导致继承错误,但当访问祖先类中的成员时每次都需要使用域限定符(类名::成员名),重点是这种继承会造成冗余,为了解决命名冲突,必须使用使用域限定符4、虚继承 virtual
当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。
注意:使用虚继承时,子类中会多了一些内容(指向祖先类继承来的成员)。5、构造函数
一旦进行了虚继承(钻石)祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的有参构造也需要在孙子类中显示调用6、拷贝构造
在虚继承(钻石)中祖先的拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先类中的内容也由孙子类负责拷贝,同理赋值构造也一样。
4. 虚函数、覆盖(重写)、多态
📌1、虚函数
类的成员函数前加 virtual 这种函数叫做虚函数。2、覆盖
在子类中会覆盖父类的虚函数3、多态
当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数,这就是多态。
5. 覆盖和多态的条件
📌1、函数覆盖的条件
必须是虚函数
必须是父子类之间
函数签名必须相同(参数列表完全一致,const 也会影响覆盖的结果)
返回值必须是同类或父子类类(子类的返回值要能向父类隐式转换)
访问属性不会影响覆盖
常函数属性也会影响覆盖
2、重载、隐藏、覆盖(重写)的区别
重载:同一作用域下的同名函数,函数签名不同(参数类型、个数、顺序、常属性不同),构成重载关系
覆盖:符合一系列条件
隐藏:父子类的同名成员如果不能构成覆盖,且能通过覆盖,那必定构成隐藏
3、多态的条件
在覆盖版本的函数中中得到的this指针依然是实际对象的地址,依然能够调用子类中的函数
1、父子类之间的函数有覆盖关系
2、父类的指针或引用指向子类的对象
3、在构造和析构函数中调用虚函数
在父类的构造函数中调用虚函数,此时子类还没有创建完成(回顾构造函数的调用过程),因此只能调用父类的虚函数,而不是覆盖版本
在子类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数
6.纯虚函数和抽象类
📌1、纯虚函数
在虚函数的声明的后面添加 =0,这种虚函数叫纯虚函数,可以不实现,如果实现必须在类外(只能在父类中的构造函数、析构函数中才能调用)
virtual 返回值 函数名(参数) = 0;2、抽象类
成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化(不能创建对象)
抽象类必须要被继承且纯虚函数被覆盖后,由子类实例化对象
如果继承了抽象类,但没有覆盖纯虚函数,那么子类也将成为抽象类,不能实例化3、纯抽象类
所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类
这种类一般用来设计接口,这种类在子类被替换后不需要修改或少量的修改即可继续使用。