继承
虚函数
虚函数的动态加载
当我们使用基类的引用或指针调用基类中定义的一个函数时,运行时才会知道他到底执行的哪个版本的函数。
而非虚函数或者通过对象调用时,编译的时候就决定调用哪个版本的函数
#include <iostream>
using namespace std;
class PP1
{
public:
void Print1()
{
cout << "PP1" << endl;
}
virtual void Print2()
{
cout << "PP2" << endl;
}
};
class PP : public PP1
{
public:
void Print1()
{
cout << "PPP1" << endl;
}
void Print2() override
{
cout << "PPP2" << endl;
}
};
int main()
{
auto* p = new PP();
p->Print1();
p->Print2();
auto* p2 = (PP1*)p;
p2->Print1();
p2->Print2();
}
输出结果:
PPP1
PPP2
PP1
PPP2
指定调用某个类的虚函数
调用虚函数前,申明类
auto* p = new PP();
p->Print1();
p->PP1::Print2();
纯虚函数
虚函数后面加=0就是纯虚函数,含有纯虚函数的类是抽象基类,是不能直接创建对象的。
而且派生类必须给纯虚函数定义,否则他们仍是抽象基类。
注:纯虚函数不能在声明里定义,但可以在外部定义
protected
介于public和private中间的字段:外部不可直接访问,但是派生类内可以访问
改变基类成员的访问级别
在对应的级别,使用using声明语句
public:
using base::pri_data;
默认的继承级别
struct默认继承public类型,class默认继承pri类型。
最好声明的时候,显示的声明出来。
继承构造函数
c++11功能。通过【using 基类:基类】的写法可以继承基类的构造函数
注意:如果基类有默认实参,派生类不会继承。派生类回生成多个构造函数,比如基类是一个形参+一个实参,派生类会生成一个形参的构造函数+两个形参的构造函数
注意2:默认、拷贝和移动构造函数不会被继承
其他注意事项:
- 即使形参列表不同,派生类的同名成员也会隐藏掉基类的同名成员
多重继承
多重继承主要需要注意的就是成员的二义性。如果两个基类有同一个名称的成员,需要在前面声明类名。
构造和析构顺序
构造顺序:先父类,后子类
析构顺序:先子类,后父类
虚继承
解决问题:同一个类多次继承一个类的问题。B、C分别继承A,D继承B、C,此时D继承了两次A。使用虚继承可以确保在D中只有一个A的基类部分。
虚基类:虚继承某个类的类
虚派生只影响从指定了虚基类的派生类中进一步派生出的类,它不会影响派生类本身。
class Raccoon : public virtual ZooAnimal {};
class Bear : virtual public ZooAnimal {};
构造顺序
首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序依次对其进行初始化。
比如上面的ABCD。构造顺序是:A->B->C->D
如果未声明虚基类,构造顺序是:A->B->A->C->D
虚函数总是先于非虚基类构造,与他们在继承体系中的次序和位置无关
析构顺序
和构造顺序相反
语法/关键字
final(类)
如果不希望该类被其他类给继承,就在类名后申明final
class Last final
{
}
final(方法)
如果不希望该方法被派生类给覆盖,就在方法后申明final
struct A {
void Print() const final;
}
override
如果写的函数没有覆盖到基类的虚函数,会报错
其他
派生类中删除的拷贝控制和基类的关系
- 如果基类中的三个构造函数或析构函数是删除或不可访问的,则派生类对应成员将是被删除的
- 如果基类析构函数不可访问或删除,则派生类中【合成】的默认和拷贝构造函数将是删除的
- 如果基类的移动函数是删除的,则派生类中该函数将是删除的。基类的析构函数如果是删除的同理
tips
- 定义析构函数将阻止合成移动操作
- 如果已经定义了拷贝相关函数,那么就不会生成移动相关操作;反之亦然
- 当一个类拥有多个基类时,有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时,不加前缀限定符直接使用该名字将引发二义性