目录
一、多态是什么?如何实现?
二、 什么是重写?有什么特点?
三、什么是协变?
四、析构函数能实现多态吗?为什么要实现?
五、override和final的作用是什么?
六、 多态的原理是什么?虚表/虚函数表
七、虚表保存在哪里?
八、坑题
九、多态分几种?哪几种?
十、多继承+虚函数重写(多个父类的类会有几个虚表?)
一、多态是什么?如何实现?
多态:不同的对象调用一个同名函数时,实际调用的是不同的函数,会有不同的行为或结果,比如:高铁票买票时有学生票和成人票。
可以通过虚函数来实现:父类定义了虚函数(用virtual修饰的函数),子类重写该函数,通过基类的指针或引用来调用虚函数。
构成多态的两个条件:①重写了虚函数 ②通过父类的指针或引用调用虚函数
二、 什么是重写?有什么特点?
重写(覆盖)是指在派生类中有一个跟基类返回值,函数名,形参列表相同(缺省参数除外)的虚函数。
重写时,只是重写函数体的部分,函数头用的是父类的,也就是说,如果有的话,缺省参数也是用的父类的。派生类重写时,可以不加virtual。
三、什么是协变?
虚函数返回值不同时,必须满足协变。协变的返回值可以不同,但协变的返回值必须是父子关系(不一定是当前的父子类,可以是任意别的父子类)的指针或引用(同时是指针或同时是引用)。
四、析构函数能实现多态吗?为什么要实现?
1)析构函数加上virtual关键字是可以实现多态的,虽然表面上看他们名字不同,但实际上编译器会将析构函数的名字统一处理成destrutor。
2)如果析构函数不实现多态,会出现下面的问题:一个父类指针类型有可能指向父类,也有可能指向子类,实现多态才能在delete这个指针时,调用它真正指向的类型的析构函数。
五、override和final的作用是什么?
1)override关键字用于显式地声明一个成员函数是重写其基类中的虚函数,以确保正确的多态行为,可以增加代码的可读性,并且在子类中改写基类的虚函数时会让编译器在发现错误时给出警告或错误提示,有助于避免因为拼写错误或其他原因导致的潜在问题。
2)final可以用来修饰一个类或一个函数:
修饰一个类时,表示该类不可被继承。
修饰一个函数时,表示该函数不可被重写。
扩:不用final,如何让一个类不可被继承?
将构造函数私有化,并设置一个静态成员函数来调用其私有的构造函数。
六、 多态的原理是什么?虚表/虚函数表
1)父类中会有一个虚函数表指针类型的成员,创建父类对象后,该指针指向父类的虚函数表,该虚函数表保存的是父类虚函数指针;
2)子类继承父类后,会通过继承得到一个虚函数表指针类型的成员,创建子类对象后,该指针指向子类的虚函数表,该虚函数表保存的是子类虚函数指针。
同一个类的多个对象中的vfptr(虚函数表指针)指向的是同一个虚函数表。
下图可以验证上面的说法:
当test(p)执行时,pp是p对象的引用,pp看到的是这一部分(父类的虚函数表):
当test(s)执行时,pp是s对象的切片,pp看到的是这一部分(子类的虚函数表):
七、虚表保存在哪里?
验证猜测是在常量区。
八、坑题
正确答案是:B
九、多态分几种?哪几种?
多态分为静态多态和动态多态:
① 静态多态又称静态绑定,是指在程序编译期间就确定了程序的行为。(函数重载)
② 动态多态又称动态绑定,是指在程序运行期间才确定程序的行为。(继承、虚函数重写)
十、多继承+虚函数重写(多个父类的类会有几个虚表?)
Derive对象是多大?有几个虚表指针?
20字节,两个虚表指针。
为什么Derive对象重写fun1后,两个虚表保存的函数指针不一样,调用的结果是同一个?
上面这段代码中,ptr123调用的都是同一个函数,也就是d对象的func1,而调用d对象的成员函数是要给它传d对象的this指针的,对于ptr1,由于它的地址与d对象的地址是刚好重合的,所以其虚函数表里保存的func1的地址就是真正的地址;ptr2则需要重新修正this指针的位置,让其指向d对象的起始地址。