扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
//4.cpp 15. #include <iostream.h > 16. class shape{ 17. public: 18. virtual void draw(){cout<<"shape::draw()"<<endl;} 19. virtual void area(){cout<<"shape::area()"<<endl;} 20. void fun(){draw();area();} 21. }; 22. class circle:public shape{ 23. public: 24. void draw(){cout<<"circle::draw()"<<endl;} 25. void adjust(){cout<<"circle::adjust()"<<endl;} 26. }; 27. main(){ 28. shape oneshape; 29. oneshape.fun(); 30. 31. circle circleshape; 32. shape& baseshape=circleshape; 33. baseshape.fun(); 34. }编译器在编译上面这段代码的时候将为这shape和circle两个对象分别建立一个VTABLE表,这些表依次填充派生类对象和基类对象中声明的所有的虚函数地址。如果派生类本身没有重新定义基类的虚函数,那么填充的就是基类的虚函数地址。这样一旦如果函数调用一个派生类不存在的方法时候能够自动调用基类方法。然后编译器在每个类中放置一个vptr,一般置于对象的起始位置,继而在对象的构造函数中将vptr初始化为本类的VTABLE的地址。整个结果布局如下。
circle circleshape; shape& baseshape=circleshape; baseshape.fun();函数进入fun函数之后,函数的this指针将指向basefun对象,另一方面basefun指向一个circleshape,因此this指针指向的实际上为circleshape对象,而circleshape的vptr指针指向circle类的虚拟函数表,这样编译器将从虚拟表中取出circle::draw()和circle::area()的地址,进行连接。因为circle本身没有重新定义area()方法,因此编译器使用shape的area()方法。如图四。
35. #include36. class shape{ 37. public: 38. virtual void draw(){cout<<"shape::draw()"<draw();//OK 51. oneshape->adjust();//错误,编译器无法通过 52. 53. circle* circleshape; 54. circleshape->adjust(); 55. }
在程序编译期间,由于oneshape为shape类型的,因此它将检查shape的虚拟函数表,发现VTABLE[0]为draw函数的地址,于是翻译成p->VTABLE[0]。未来执行期间,p 实际上指向的是circle对象,因此真正调用的为circle->VTABLE[0]处的函数,即circle::draw。同样对于adjust函数,C++ 编译器也会去检查shape的VTABLE,结果编译器无法找到adjust函数,因此编译无法通过。 对于circleshape,因为它是circleshape类型的,因此它将会检查circle的VTABLE,得知VTABLE[2]处为adjust的地址,因此编译器翻译成call circleshape->VTABLE[2],真正执行时候circleshape为circle类型,因此它将绑定circle的VTABLE[2]处的函数即circle:: adjust()。 就这样,编译器借助虚拟函数表实现了动态联编的过程,从而使多态的实现有了可能。因此说虚拟函数表是多态性的幕后功臣一点也不为过。
五 结束语
多态性的实现是一个非常复杂的过程,上面的讨论仅仅是针对简单继承而言,即基类只有一个的情况,对于多重继承,情况又会有所改变。本文仅是抛砖引玉,希望有兴趣的朋友可以一起探讨。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者