多态(C++)
一、(1)编译时(静态) 函数重载,函数模板
(2)运行时(动态) 重点
二、多态的定义及实现
1.构成条件
多态是一个继承关系下的类对象,去调用同一函数,产生不同的行为
1.1必须重要条件
a.必须是基类(父类)的指针或引用调用函数
b.被调用的函数必须是虚函数


其中基类的virtual可以不写,但是不建议,而且这也是考试的埋坑处
1.2虚函数的重写/覆盖:基类中有一个跟派生类完全相同的虚函数(即返回值类型,函数名字,参数列表(指类型int,char...))也称三同
接下来我们看一道知道答案想给面试官背后来个板砖的题

这里按照常规思路,调用test(),而父子类func构成多态(与参数的值无关,类型相同即可),
那么大部分人都会觉得是A->1,这就调到的出题人的坑里,这就考验大家在平时对于细节的掌握,其实构成多态是形成如下的函数

由于大部分例子都是参数的值也相等,所有大部分人会忽略这个细节,所有答案其实是B->1.
那如果是这呢

这里是子类的指针,所以不构成多态,故只是函数的调用

对了

当B的指针传入test中时,是A* this这也是大家需要注意的地方。
1.3协变
派生类重写基类函数时,与基类函数返回值类型不同,即基类的虚函数返回基类的指针或引用,而派生类返回派生类的指针或引用。
协变意义不大,不建议使用
1.4析构函数的重写
基类的析构函数为虚函数,此时派生类析构函数只需要定义,无论是否加virtual,虽不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,故基类加virtual,就构成了重写。如果~A()不加virtual,那么delete p时,只调用A的析构,没有调用B的析构,导致内存泄漏,这是因为~B()在释放资源。



这里有两次A的析构的原因在继承中我们已经详细讲解过了,子类会自动调用父类的析构
所以这里建议基类的析构设计成虚函数
1.5override和find
在派生类重写后面加override,用来检查函数名是否相同,如果我们不想让派生类重写这个虚函数,用final修饰
1.6相同函数名之间的关系

再看一道例题

答案是12,只要有虚函数,就需要考虑虚表指针(独立),其在32位下是4字节,64位下是8字节
2、纯虚函数和抽象类
在虚函数后面加上=0,不需要定义实现,只要声明即可。包含虚函数的类叫做抽象类,抽象类不能实例化出对象
3、虚函数表
1>基类对象的虚函数表中存放所有虚函数的地址
2>派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的是这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同一个,就想基类对象成员和派生类对象中的基类对象成员也独立
3>派生类中重写的基类的虚函数,派生类的虚函数就会被覆盖为派生类重写的虚函数地址
4>派生类的虚函数表中包含:基类的虚函数地址,派生类重写的虚函数地址,派生类自己的虚函数地址
5>虚函数表本质是一个存虚函数指针的指针数组
6>虚函数和普通函数一样,编译好后是一段指令,都是存在代码段的,只是虚函数的地址又存到了虚表中
7>C++标准中并没有严格规定虚函数表存在在哪
4、静态绑定和动态绑定
a.对不满足多态条件(指针或引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定
b.满足多态条件的函数调用是在运行绑定,也就是在运行时指向对象的虚函数表中找到调用函数的地址,也叫做动态绑定