多态是OOP中一个十分重要的特性,至于如何使用可以参考这篇C++与JAVA多态相同与不同,相信对于学习C++和java的人都有帮助。
多态实现的关键技术是动态绑定。
动态绑定:程序在运行期间寻找函数地址
静态绑定:代码在编译时已经确定了函数地址
多态的实现表象是指针+虚函数,本质是虚表+虚指针。
这里有一篇博客写的很全面,可以参考一下c++多态实现的机制
1.虚表
声明了虚函数的类会隐式创建一个虚指针指向虚表,而虚表保存了虚函数的地址(虚表中只有虚函数,非虚函数在另一个表中),当我们调用虚函数时,实际上是通过虚指针去查找虚表中对应虚函数的地址
2.虚指针
虚指针指向类的虚表,属于类的成员变量,可以继承,当子类继承父类的虚指针时,子类的虚指针指向子类的虚表。虚表也可以继承,也可以理解为继承了虚函数,然后创建了自己的虚表,所以如果没有重定义父类虚函数,那么子类虚表和父类虚表完全相同;重定义的虚函数在子类虚表中的地址会改变。
3.虚指针的初始化
虚指针在构造函数中进行初始化,指向类的虚表。如果类B继承了类A,那么我们知道生成B的实例对象时,会先调用A的构造函数,此时虚指针指向A的虚表,然后调用B的构造函数,此时虚指针指向B的虚表,所以当B的实例构造完成后,虚指针指向B的虚表。
4.多态
用下面的例子讲解,当我们声明一个父类A的指针p指向子类B的对象时,在编译阶段是不分配内存的(不构造对象的),如果想了解编译过程做了什么,可以参考一下这篇C程序编译过程浅析。也就是说编译器并不知道指针p指向的是B类的对象,只知道这是一个A类的指针,那么在编译p->func()时,会直接查找A中func的地址并替换(可以简单理解为这样),也就是静态绑定,那么在运行时自然调用的就是A的func了
1 class A 2 { 3 public: 4 A(){}; 5 void func() 6 { 7 cout << "A func called!"; 8 } 9 }; 10 11 class B:public A 12 { 13 public: 14 B(){}; 15 void func() 16 { 17 cout << "B func called!"; 18 } 19 20 }; 21 22 int main() 23 { 24 A*p = new B(); 25 p->func(); 26 return 0; 27 }
但是如果将func声明为虚函数,如下,那么在编译时编译器一看func是虚函数,会直接跳过,那么在运行时,B的对象已经被构造出来了,那么p所指向的B对象的虚指针已经指向了B的虚表,此时调用p->func()时,操作系统会在p的虚表中去寻找func这个函数,也就是动态绑定,然后调用,这时候调用的自然就是B的func了,可以说虚指针和虚表就是为了多态而生的。
1 class A 2 { 3 public: 4 A(){}; 5 virtual void func() 6 { 7 cout << "A func called!"; 8 } 9 }; 10 11 class B:public A 12 { 13 public: 14 B(){}; 15 void func() 16 { 17 cout << "B func called!"; 18 } 19 20 }; 21 22 int main() 23 { 24 A*p = new B(); 25 p->func(); 26 return 0; 27 }
看到这里,大家再去看我写的C++与JAVA多态相同与不同,也就明白为什么我说重写和重定义不同了。