多态的实现原理:
首先介绍下函数重写 重定义 重载的区别;
函数重写:
发生在父类和子类之间,子类将父类中的同名函数进行了覆盖,如果在函数前面含有virtual那么就是重写,如果没有就成了覆盖,会子类的同名函数将会覆盖(隐藏)父类的同名函数,如果想调用的话,那么就要使用::作用域运算符
函数重载:
函数重载发生在同类之间,平等关系。在不同的类对函数看似重载的操作是不成立的
多态理论基础:
静态联编:
是程序的匹配,连接在编译阶段实现,也成为早起匹配,重载使用的就是静态联编
动态联编译:
是指程序联编推迟到运行时进行,又成为迟绑定
在编译阶段,编译器自动根据指针的类型判断是执行的父类对象还是子类对象:出于程序安全的角度讲,编译器认为父类指针指向父类对象,子类指针指向子类对象。这就是静态联编的结果
(一)谈谈你对多态的理解?
答:
1. 多态的实现效果,同样的调用语句有不同的表现形态:
2. 多态实现有三个条件?
有继承,有虚函数重写,有父类指针指向子类对象:
3. Virtual关键字告诉编译器这个函数支持多态,不要根据指针的类型进行调用,而是根据指针所指向的具体的对象,进行函数调用
4. 理论基础:
动态联编PK静态联编,根据实际的对象的类型来判断重写函数的调用
5. 多态的重要意义:
设计模式的基础,是代码框架的基石
(二)是否每个类的成员函数都声明为虚函数,为什么??
答.可以声明为虚函数,但是在在定义对象的时候,会生成一个虚函数表,而虚函数在调用的时候是通过寻址实现的,这回影响代码的执行效率
(三)构造函数中调用虚函数能实现多态吗?
答.不可以,因为多态的vptr指针是分布初始化的,在子类进行初始化的时候先调用父类的构造函数,这时候子类的父类的Vptr指针都是指向父类的,因此产生不了多态。
(四)为什么要定义虚析构函数?
答.因为virtual可以指引delete运算符正确析构动态对象
(五)重点:
就是基类指针指向父类对象还是子类对象???我们在分析的时候是这么分析的,如果基类指针指向父类对象,那么就去调用父类的对象;如果指向子类对象,就去调用子类对象。
但是实际情况是这样的,编译器压根就不是这么做的。而是根据传递来的是什么对象,然后看此函数是否是虚函数,如果是虚函数,就去找这个对象对应的虚函数表,然后调用响应的函数。
问题引出:赋值兼容性遇上函数重写
#include "iostream" using namespace std; //定义一个子类和一个父类 class Parent { public: protected: private: }; class Child : public Parent { public: protected: private: }; //面向对象新需求 //如果我传一个父类对象,执行父类的print函数 //如果我传一个子类对象,执行子类的printf函数 //现象产生的原因 //赋值兼容性原则遇上函数重写 //1 //2 //3 //静态链编 void HowToPrint1(Parent *p) { } void HowToPrint2(Parent &myp) { } void main() { } |
多态的理解 角度1: p->print(); //一句话,有多种效果,有多种表现形态把。。。//这个功能的就是多态 角度2: |
1、面向对象新需求
编译器的做法不是我们期望的 |
2、C++提供的多态解决方案
-
C++中的多态支持
-
C++中通过virtual关键字对多态进行支持
-
使用virtual声明的函数被重写后即可展现多态特性
3、重载、重写、重定义
函数重载 必须在同一个类中进行 子类无法重载父类的函数,父类同名函数将被名称覆盖 重载是在编译期间根据参数类型和个数决定函数调用 函数重写 必须发生于父类与子类之间 并且父类与子类中的函数必须有完全相同的原型 使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义) 多态是在运行期间根据具体对象的类型决定函数调用 |
经典易错:
|
class Parent01 { public: public: }; //重写的两种情况 //如果函数重写,在父类中增加了virtual关键字, //如果函数重写,没有加virtual关键字,,相当于在子类中重定义。。。。。,不会发生多态。。。 class Child01 : public Parent01 { public: //原因是发生了 }; int main() { //子类和父类有相同的名字(变量名字或者是函数名字的时,子类名字覆盖父类名字,如果想使用父类的资源,需要加::) } //问题1:child对象继承父类对象的func,请问这句话能运行吗?why //c.func(); //1子类里面的func无法重载父类里面的func //2当父类和子类有相同的函数名、变量名出现,发生名称覆盖 //3//c.Parent::func(); //问题2 |
4、多态的实现原理以及多态的理解
多态的实现效果 多态:同样的调用语句有多种不同的表现形态; 多态实现的三个条件 多态的C++实现 多态的重要意义 实现多态的理论基础 |
C++中多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表 虚函数表是一个存储类成员函数指针的数据结构 虚函数表是由编译器自动生成与维护的 virtual成员函数会被编译器放入虚函数表中 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针) |
|
|
说明1: 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。 说明2: 出于效率考虑,没有必要将所有成员函数都声明为虚函数 |
5、多态原理研究(证明VPTR指针的存在)
C++编译器内部实现的时候,通过virtual关键字,内部帮我们在父类子类添加了虚函数指针和虚函数表,以下是证明虚函数指针的存在的方法 |
#include "iostream" using namespace std; class AA { public: protected: private: }; void main() { } |
6、虚函数表指针(VPTR)被编译器初始化的过程
|
对象在创建的时,由编译器对VPTR指针进行初始化 只有当对象的构造完全结束后VPTR的指向才最终确定 父类对象的VPTR指向父类虚函数表 子类对象的VPTR指向子类虚函数表 |
7、为什么要定义虚析构函数
//在父类中声明虚析构函数的原因 //通过父类指针,把所有的子类析构函数都执行一遍。。。 void howtoDel(Parent *pbase) { } void mainobj() { } void main() { } |
8、基类和子类对象指针++混搭风
class Parent01 { protected: public: }; //一次偶然的成功,比必然的失败更可怕 class Child01 : public Parent01 { public: public: }; void howToF(Parent01 *pBase) { } int main() { // // // // } |