C++的多态必须满足两个条件:
1 必须通过基类的指针或者引用调用虚函数
2 被调用的函数是虚函数,且必须完成对基类虚函数的重写
虚函数:只能是类中非静态成员函数(virtual)(构造函数不可,析构函数可以定义为虚函数)
虚函数重写(覆盖):子类与父类中的虚函数有相同的名字,返回值,参数列表
子类重写虚函数可以不加virtual
析构函数的重写:只要父类的析构函数用virtual修饰,无论子类是否有virtual,都构成析构
用final修饰的函数无法重写,用override修饰的需要重写
协变(虚函数重写的例外)
子类的虚函数与父类的虚函数返回值可以不同,也能构成重载。但子类的返回值是子类的指针或引用,父类的返回值是父类的指针或引用,且返回值代表的两个类也成继承关系
class Person
{
public:
virtual Person* fun()//返回父类指针
{
cout << "Person->fun()" << endl;
return nullptr;
}
};
class Student
{
public:
//返回子类指针,虽然返回值不同,也构成重写
virtual Student* fun()//子类重写父类虚函数
{
cout << "Student->fun()" << endl;
return nullptr;
}
};
重载、重写(覆盖)、重定义(隐藏)
1、重载:重载函数处在同一作用域,函数名相同,函数列表必须不同
2、重写:必须是虚函数,且在父类和子类中,返回值,参数列表,函数名必须完全相同(协变除外)
3、重定义:子类和父类的成员变量相同或函数名相同,子类隐藏父类的对应成员
抽象类
在虚函数后面加上=0就是纯虚函数,有虚函数的类就是抽象类(接口类)。抽象类无法实例化出对象。抽象类的子类也无法实例化出对象,除非重写父类的虚函数
class Car
{
public:
virtual void fun() = 0; //不用实现,只写接口就行。
}
作用:
强制子类重写虚函数,完成多态
表示某些抽象类
实现继承:普通函数的继承
接口继承:虚函数的继承
虚函数重写后只会继承接口,重写实现,如果不用多态,别把函数写成虚函数
class A
{
public:
virtual void fun(int val = 0)//父类虚函数
{
cout <<"A->val = "<< val << endl;
}
void Fun()
{
a->fun();//传过来一个子类指针调用fun()
}
};
class B: public A
{
public:
virtual void fun(int val = 1)//子类虚函数
{
cout << "B->val = " << val << endl;
}
};
B b;
A* a = &b;
a->Fun();
//结果是什么呢?
//B->val = 0
//子类对象切片给父类指针,传给Fun函数,满足多态,会去调用子类的fun函数,但是子类的虚函数继承了父类的接口,所以val是父类的0.
虚函数表
子类跟父类一样有一个虚表指针。
子类的虚函数表一部分继承自父类。如果重写了虚函数,那么子类的虚函数会在虚表上覆盖父类的虚函数。
本质上虚函数表是一个虚函数指针数组,最后一个元素是nullptr,代表虚表的结束。
所以,如果继承了虚函数,那么
1 子类先拷贝一份父类虚表,然后用一个虚表指针指向这个虚表。
2 如果有虚函数重写,那么在子类的虚表上用子类的虚函数覆盖。
3 子类新增的虚函数按其在子类中的声明次序增加到子类虚表的最后