在类与继承里我们说过一个基类的指针/引用可以绑定一个子类对象.
Son s;//创建一个子类对象
Father *object=s;//基类指针指向一个子类对象
//Father &object1=s;//基类引用绑定了一个子类对象
多态
多态就是指一个东西具有多种形态.具体在类里表现为:
·基类的指针/引用指向不同的对象(具有相同基类的对象),收到相同的消息,根据绑定的对象产生不同的行为.
·不同的对象指的就是由同一个基类继承的派生类.
·相同消息指的是同一个基类的派生类的同一个名字的函数被基类的引用调用了.
·产生不同的行为:会根据基类引用/指针实际绑定的对象,调用对应(派生类)版本对应的函数.
例:
#include<iostream>
class Animal
{
public:
virtual void Sound()
{std::cout<<"叫声"<<std::endl};//动物类的声音
};
class Dog:public Animal
{
public:
void Sound() override
{
std::cout <<"汪汪"<<std::endl;
}
};
class Cat:public Animal override
{
public:
void Sound()
{
std::cout <<"喵喵"<<std::endl;
}
};
int main()
{
Animal *animal=nullptr;
//根据某个方式判断animal指向哪个对象,在此不举例
if(Dog)
animal=new Dog();
else if(Cat)
animal=new Cat();
animal->Sound();//调用具体实例的对象的函数(行为)
}
在上述代码中,我们发现基类中的函数被virtual所修饰,之前提到过派生类的成员隐藏,与这个类似,被virtual所修饰的基类的成员函数我们称之为“虚函数”.(结尾会提到其细微差别)
虚函数(virtual)
virtual所修饰的基类的成员函数我们称之为“虚函数”.
作用:实现动态多态.
在设计基类的时候,有些成员希望派生类去重写去定义该函数以覆盖(重写)基类的版本.这种函数就应该声明为虚函数.
声明为虚函数之后特点:
·当基类指针/引用调用虚函数的时候,该过程会发生动态绑定.
·会根据基类指针/引用实际绑定的对象来确定应该调用哪个版本的虚函数.
虚函数表
虚函数是实现动态多态的一个机制,我们需要知道调用虚函数的底层逻辑.
·虚函数表(函数指针数组)是一个存储了虚函数的地址的数组,每一个包含虚函数的类都有一个对应的虚函数表.
·基类和派生类分别拥有自己的虚函数列表,虚函数表中存储各派生类中虚函数的地址,派生类的虚函数会继承并扩展基类的虚函数列表.这个表是共享给所有对象的.
虚函数表指针在对象的哪里
虚函数指针通常位于对象内存起始,即对象的地址.通过对象的地址可以访问到虚函数表,并通过虚函数表来调用对应的虚函数.
动态绑定和静态绑定
·动态绑定是在运行时确定调用的虚函数,根据动态类型确定调用哪个版本的虚函数
·静态绑定是在运行前就已经确定的,根据静态类型确定调用哪个版本的虚函数
一般情况下,指针/引用的类型要与它指向的实际类型一致.
但是有两种特殊情况:
·允许指向常量的指针,指向一个非常量的对象,但是,非常量的指针不能指向一个常量.
·允许将一个基类指针/引用指向一个派生类的对象.
派生类中的虚函数
但我们在派生类中定义与基类函数原型相同的虚函数时,并不一定需要再用virtual来修饰.
·函数原型相同:新定义的虚函数要全部与基类的虚函数一致,除了函数体.
c++提供了一个保留字override:重写,用来说明某一个函数是覆盖基类的虚函数的,建议加上,可以用来检测.
如果基类的某一个函数不想被派生类新定义的所覆盖,可以使用final修饰,表示是最终版本,不可重写.
在一个类后面用final修饰,表明这个类不允许被继承.
虚函数回避机制
注:
成员函数的隐藏,重写,重载分别是什么意思,有什么区别.
·重载:指在同一个可以访问的区域内被声明了几个具有不同参数(参数类型,参数个数,参数顺序)的同名函数,调用时会根据参数列表自动确定调用哪个函数,重载不关心函数的返回值.
·重写:是指在派生类中存在重新定义的函数,其函数名,参数列表,返回值类型,修饰关键字所有都必须和基类被重写的函数一致.除了函数体可以不同,派生类调用的时候会调用派生类的重写函数,不会调用被重写的函数,重写的基类必须要有virtual修饰.在派生类中可以不用再写virtual修饰.
·隐藏:是指派生类的函数屏蔽了与其基类同名的函数,只要函数同名了,基类的就会被隐藏.