多态
多态
多态条件:
1.虚函数重写
2.父类的指针或引用调用虚函数
虚函数重写的条件: virtual + 三同(函数名+参数+返回类型)
不构成重写,就是隐藏
特例1:子类的虚函数可以不写virtual,依旧构成重写(实际最好写上),因为重写是重写实现,继承父类的接口,就相当于也是virtual
特例2:重写协变。返回值可以不同,但必须是父子关系的指针或引用,且父对应父,子对应子。
特例3:在特殊情况下,为了使得析构函数的调用正常,特殊处理了析构函数(destructor),这样就构成了多态
重载:两个函数在同一作用域
函数名相同,参数不同(顺序,个数,类型)
重写(覆盖):两个函数分别在基类和派生类的作用域
函数名/返回类型/参数相同(协变例外) ,两个函数必须是虚函数
重定义(隐藏) 两个函数分别在基类和派生类的作用域
函数名相同 不构成重写就是隐藏
final:
a.修饰类,使类不能在被继承
b.修饰虚函数,使虚函数不能被重写
override:
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口,重写了接口的实现。
多态的原理
虚表
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
在类中有一个指向函数指针数组的指针,指向虚表。
虚表存在哪里:
1.通过实验证明,虚函数表存在代码段。
2.虚函数表在编译时就生成好了,在初始化列表里初始化。
class Base
{
public:
virtual void func()
{
cout << "Base::virtual void func()" << endl;
}
};
class Derive : public Base
{
public :
virtual void func()
{
cout << "Derive::virtual void func()" << endl;
}
};
int main()
{
Base* pb = new Derive;
pb->func();
return 0;
}
满足多态的条件,在运行时会去当前对象的虚表中找对应的虚函数,所以指针指向父类调父类的虚函数,指针指向子类调用子类的虚函数
动态绑定和静态绑定
静态绑定是在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
动态绑定是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。