@TOC

1. 多态概念

就是多种形态, 具体为 去完成某个行为,当不同对象完成时会产生不同的状态


如买票这种行为,普通人是全价买票,而学生是半价买票,军人则是优先买票

【C++】多态(上)_子类


又或者支付宝扫码领红包,不同的人扫到的钱是不一样的

【C++】多态(上)_子类_02


2.多态的定义和实现

1.多态的构成条件

多态的条件: 1.虚函数的重写----三同 (函数名、参数、返回值相同) 2.父类的指针或者引用去调用

无法使用父类对象调用

【C++】多态(上)_虚函数_03

当把函数的参数从父类的引用改成父类本身时,发现不能实现多态了(具体为什么后面解释)


不满足多态条件的调用

【C++】多态(上)_虚函数_04

不满足多态的虚函数重写的条件 则看调用者的类型,调用这个类型的成员函数 此时属于Person类,所以两次都会调用父类的BuyTicket函数

2. 虚函数

被virtual修饰的类成员函数被称为虚函数


虚函数的重写/覆盖

派生类中有一个跟基类完全相同的虚函数(即两者的返回值类型、函数名字、参数列表都相同),称子类的虚函数重写了基类的虚函数


【C++】多态(上)_虚函数_05

此时的preson中的BuyTicket函数与 Student类中的BuyTicket的函数构成重写/覆盖


传递不同的对象调用不同的函数 传父类调用的是父类的虚函数 传子类调用的是子类的虚函数

【C++】多态(上)_虚函数_06

person &p=st; 由于stuent类属于person类的子类,所以p是stuent类中属于父类那一部分的别名 传递父类对象ps,调用 Person类中的BuYTicket函数 传递子类对象st,调用 Student类中的BuYTicket函数


虚函数重写的例外

1. 子类的虚函数可以不加virtual

【C++】多态(上)_虚函数_07

子类重写了虚函数,重写体现了接口继承 子类把函数的声明 继承下来,重写的是函数的实现, 所以不写也可以,因为它继承父类的接口,重写实现,满足多态的条件


2.协变 返回值不同,但必须是父子关系的指针或者引用

【C++】多态(上)_虚函数_08

协变的实际作用不是很大,可能在某些特殊场景可以用到

例题

【C++】多态(上)_虚函数_09


父子类的func函数构成多态 1.虚函数的重写 虽然子类并没有写,但是由于例外,子类的函数会继承父类的函数的virtual 2.调用父类的指针或者引用 在父类的test函数中 存在隐藏的this指针,该this指针类型为 A*

【C++】多态(上)_虚函数_10

使用父类的指针去调用,所以满足多态条件


【C++】多态(上)_子类_11

子类的对象传给父类的指针,实际上相当于 父类的指针指向子类中父类的那一部分 由于满足多态,指向谁调用谁,所以调用的是子类的func函数


子类把父类的函数的声明 继承下来,重写的是函数的实现 所以实际上父类的缺省val值会把子类的缺省val值覆盖掉 子类的func函数 就变为val为1,输出B->1

变形题

【C++】多态(上)_虚函数_12

结果为B->0


【C++】多态(上)_虚函数_13

由于不满足父类的指针或者引用去调用 所以不构成多态 那虚函数的重写也就不能生效,所以在子类中的func函数中的缺省val值以及为0 不构成多态,则看调用者的类型,调用这个类型的成员函数 所以调用子类的func函数,最终输出 B->0

3.C++11 override 和final

final

修饰虚函数,表示该虚函数不能被重写

【C++】多态(上)_子类_14


overrride

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

【C++】多态(上)_虚函数_15

4.重载 、覆盖(重写)、隐藏(重定义)的对比

【C++】多态(上)_父类_16

5. 抽象类

在虚函数后面写上=0,这个函数被称为纯虚函数,包含纯虚函数的类叫做抽象类,抽象类不能实例化对象

【C++】多态(上)_虚函数_17


【C++】多态(上)_虚函数_18

若创建一个子类继承抽象类,则该子类也包含纯虚函数,子类也会变成抽象类,所以子类创建对象也会报错

6. 多态原理

虚函数表

常考笔试题 sizeof(Base) 大小是多少?


【C++】多态(上)_子类_19

若以结构体的内存对齐考虑,则大小应为8字节,但是实际上为12字节


【C++】多态(上)_虚函数_20

_vfptr代表虚函数表指针,加上虚函数指针,内存对齐后字节大小为12

多态的原理

【C++】多态(上)_父类_21

BuyTicket不是虚函数重写时,不构成多态,生成的汇编指令 不构成多态,p.BuyTicket() 调用时,就看p的类型 ,此时p的类型为Person,所以传的是 Person::BuyTicket的地址


【C++】多态(上)_子类_22

构成多态时,多态调用转换成的汇编指令


【C++】多态(上)_父类_23

p.BuyTicket()是不知道自己要调用那个的,通过查看传递过来的对象做出判断, 若为父类对象,则p->BuyTicket在mike的虚表中找到虚函数 Person::BuyTicket 若为子类对象,则p->BuyTicket在iohnson的虚表中找到虚函数 Student::BuyTicket

虚函数表 本质是一个虚函数指针数组

为什么叫做覆盖?

【C++】多态(上)_子类_24

父对象和子对象都调用BuYTicket函数时,由于构成多态,要进行虚函数重写,所以子类的虚函数指针数组是由父类的虚函数指针数组拷贝过来的,再向其中填入新的地址,造成覆盖 而没有被重写的Func函数则没有被覆盖

为什么父类对象不可以实现多态?

【C++】多态(上)_父类_25

若为父类的对象,则不可以实现多态


【C++】多态(上)_父类_26

查看汇编时,直接就去调用Peson::BuYTicket的地址


【C++】多态(上)_虚函数_27

若为指针或者引用,将子类中属于父类那一部分切出来,使指针指向属于父类那一部分,或者作为属于父类那一部分的别名子类的虚表还是子类的


【C++】多态(上)_虚函数_28

若为父类对象,就会把子类中属于父类的那一部分拷贝给父类,有可能把子类的虚表也拷贝给父类若拷贝成功,则父类对象的虚表就不知道是父类的虚表还是子类的虚表了