概念
多态就是指在完成具体的行为时,不同的对象会产生不同的结果。就像执行一个函数时,传入两个不同的对象,产生的结果不同。
虚函数
在继承的时候,可以在继承方式前加virtual关键字来声明函数是虚拟继承。
虚函数就是在类的函数成员前加virtual关键字。
虚函数是在编译阶段实现的,一般存在于代码段。
虚函数的重写(覆盖):子类中有一个虚函数和父类中虚函数完全相同,即返回值,函数名,函数参数都相同。有一个例外,函数返回值是基类与派生类时依旧构成函数重写(协变)。
多态实现的前提
- 调用函数的对象必须是指针或者引用。
- 被调用的函数必须是虚函数,且完成了虚函数的重写。
重写(覆盖),重定义(隐藏),重载
重写:函数分别在基类与派生类两个不同的作用域中;两个函数都是虚函数,函数返回值,函数名;参数都相等(协变除外)。
重定义:函数分别在基类与派生类两个不同的作用域中;函数名相同;同名函数只要不构成重写就是重定义。
重载:函数在同一作用域中;但是函数名相同,参数列表必须不同。
抽象类(也叫接口类,体现接口继承)
在虚函数后面加=0(virtual void func()=0;)这个函数就叫做抽象类
包含虚函数的类叫做抽象类,抽象类不能实例化,派生类基础后也不能实例化,除非纯虚函数被重写。
可以在派生类的虚函数后家override来强制重写基类中的纯虚函数,如果没有重写,则编译报错。
多态的实现原理
如果一个函数中包含虚函数,那么它的对象就会有一个_vfptr指针存在,_vfptr指针指向虚函数表(虚表),所以_vfptr叫做虚函数表指针。
虚函数表本质是一个存虚函数指针的指针数组(vs中存在代码段),这个数组最后面放了一个nullptr。
_vfptr实在构造函数中初始化的,所以构造函数不能写为虚函数。析构函数最好写成虚函数实现多态,否则如果派生类调用基类的析构函数,就会析构错误,虽然函数名不同,但是编译器做了特殊处理。
派生类的虚表就是把基类的虚表拷贝(继承)过来,如果派生类中重写了哪个虚函数,则覆盖掉原来的虚函数。如果派生类中定义了新的虚函数,则按照声明的顺序添加到虚表中。
前提中第一点必须指针或者引用调用,因为传入对象在设置传入类型时会直接调用该传入类的虚函数,不能做到多态。
前提中第二点必须是虚函数则很好理解,如果不是虚函数,就会直接调用编译好的固定的函数。
动态绑定与静态绑定
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
多继承中的虚函数表
如果一个派生类继承自多个基类,则它会有多个虚标存在,而它自身定义的虚函数会存在与第一个基类的虚函数表中。