前言这一篇开始正式展开讲多态 , 以及我们为什么要使用多态 。
多态什么是多态引用百度百科的定义:
多态(Polymorphism)按字面的意思就是“多种状态” 。在面向对象语言中 , 接口的多种不同的实现方式即为多态 。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术 , 赋值之后 , 父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”) 。简单的说 , 就是一句话:允许将子类类型的指针赋值给父类类型的指针 。
我的理解是:子类可以通过父类的指针或者引用 , 调用子类重写父类的虚函数 , 以达到一个类型多种状态的效果 。
这听起来好像没有什么 , 我可以直接通过子类的对象调用成员函数不就行了 , 为啥还要舍近求远将其赋值到一个父类指针再调用呢?起初学习的时候我也不懂为什么 , 直到后来我遇到了一个很典型的例子才恍然大悟 , 这个例子我会在下面讲到 。
多态的条件前面也零零散散地介绍了C++多态的条件 , 这里总结一下:
- 需要有继承
- 需要使用父类的指针或引用
- 父类需要有虚函数 , 子类要重写父类的虚函数
class base {public:virtual void do_something() //有虚函数 {cout << "I'm base class" << endl;}};class derived : public base//有继承{public:void do_something() //子类重写了父类的虚函数 {cout << "I'm derived class" << endl;}};void fun1(base &b) //父类的引用 {b.do_something();}void fun2(base *b) //父类的指针 {b->do_something();}void fun3(base b) {b.do_something();}int main() {derived d;fun1(d);//I'm derived classfun2(&d);//I'm derived classfun3(d);//I'm base classreturn 0;}
fun1()和fun2()实现的过程都是动态绑定的 , 即运行时才动态确定要调用哪个函数 。那他究竟是怎么实现的?动态绑定的原理大家还记得虚类的对象是有一个vptr , 多个同类对象的vptr指向同一个vtable 。动态绑定就是通过这个vptr间接寻址来实现的 。虽然子类对象被赋值到了父类的指针 , 但是对象的vptr是没有改变的 , 他指向的还是子类的vtable 。所以父类指针去调用某个虚函数的时候 , 就会去vtable里面找函数入口 , 那找到的自然是子类的函数入口 。所以他不是在编译期间就确定的 , 而是在代码运行到那一行的时候才找到的函数入口 。
那为什么只有指针或者引用才能达到这个效果呢?《深度探索C++对象模型》这本书对此有这样一个解释:
一个pointer或一个reference之所以支持多态 , 是因为它们并不引发内存任何与类型有关的内存委托操作 。会受到改变的 , 只有它们所指向内存的大小和解释方式而已 。
这样读起来有点拗口 , 简单讲就是指针或者引用的赋值并不会改变原对象内存里的内容 , 他只会改变对内存大小及内容的解释方式 。举个简单的例子:我将int变量的地址赋值给了char型指针 , char型指针才不管原来的变量是什么 , 他对外只解释一个字节的内容 。
文章插图
同理可知 , 子类对象的内存内容并没有发生改变 , 那么对象的vptr还是指向子类的vtable , 所以调用的还是子类的的成员函数 。而简单的上转型并不会有这样的效果 , 他会对内存进行重新分配 。
另外说一下 , 只用C++有静态绑定这个概念 , 其他面向对象类的语言都是动态绑定 。可以看出C语言的知识是很细致入微的 。
为什么要使用多态到此其实多态已经讲完了 , 铺垫了这么多前置知识 , 其实多态就这么一点点 。我主要还是想讲讲为什么要使用多态 , 只有知道了为什么 , 才能使我们在设计代码的时候考虑得到如何运用这个知识点 。我们用一个游戏的例子来说明为什么 。
推荐阅读
- C++11/14/17标准库测试代码
- C++的函数重载和函数重写
- C++中左值和右值的理解
- C 与 C++ 40 年的爱恨情仇
- 使用Photoshop智能对象调整图像大小而不会丢失质量
- 编程语言是怎样从机器语言进化到面向对象的?
- JavaScript 中如何检查对象为空
- 求职|年轻女子不找工作也不找对象,还赖床不起,遭母亲拿扫帚教育
- 面向对象编程OOP的基本概念
- spring框架之AOP面向切面编程