C++多态,虚函数小结

来源:互联网 发布:玩转财务大数据 编辑:程序博客网 时间:2024/05/16 09:17

之前学C++的时候, 课上的特别快。 很多东西当时只是会用,细节以及理解都很不到位。 时隔一年是时候重新回顾一下了。 不然之后都不好意思说自己学过C++… :)
这个仅供自己回顾使用。把自己的费解易错地方记录下来。

首先就是多态。 把一些我认为的理解要点记录一下:
1. 什么是多态
从行为(效果)上看, 多态即使相同的执行代码(或者调用方式)因参数传入不同(可以是参数类型, 也可以是参数个数,也可以只是参数的内存实体不同),而产生不同效果(实际调用了不同函数)。
针对虚函数的是动态多态性。其更加具体的效果是: 用几乎完全相同(参数类型, 个数相同)的调用方式, 达到不同的调用效果。
2. 多态的常见用法。
用法必然能体现其优点。 大体而言, 下面这个例子可以说明一个常见的情况:

class Base {    public:        virtual void print() {            cout << "This is base class func." << endl;        }};class Inherit : public Base{    public:        void print() {            cout << "This is inherit class func." << endl;        }};class Caller{    public:        void call(Base *myclass) {            myclass->print();        }};int main() {    Base *inherit = new Inherit, *base = new Base;    Caller *caller = new Caller;    caller->call(inherit);    caller->call(base);    return 0;}

考虑这段代码与具体实际的联系。在实际中, Base 可能是一个封装好的类库, 不能够修改内部代码, 但是我们可以对其进行拓展, 就比如这里的Inherit 类。
而Caller类事实上已经可能被另一个类库封装住了。我们也无法修改。换言之, 我们不能够修改调用的任何代码, 却要让程序根据传入的不同对象执行不同操作。
相比较而言, 不用动态多态时对类型的解析是个很大的问题。 固然我们(有可能)可以预先写好,但是我们不可能预先知道所有情况(即我们之后可能拓展的类型) 。基于此, 在写诸如Caller这种类库时, 其中的call函数必定传入的参数类型是基类类型。这样再结合虚函数的动态绑定, 可以在保证类库对新类型很好的兼容情况下, 还不用修改已有的代码。
3. 多态比起函数重载以及函数重定义的优势
多态和函数重载并不矛盾, 甚至可以说某种程度的互补。 函数重载要求函数名相同但是函数参数类型或个数不同。 而子类虚函数要求函数参数必须和基类完全相同。 两者互不影响。
函数重定义来讲, 上文有提到。 因为Caller类不可能知道用户自定义的类型, 所以只能用基类来调用。而对于函数重定义, 就是“传入什么类型的参数我就只调用其静态类型所对应的同名函数”。这样显然就无法支持用户自定义的拓展类型了。


然后是虚函数。 其作用实际上就是为了实现动态多态性
虚函数的动态多态性完全是由虚函数表来实现的。具体可参照这个地址:
http://www.cnblogs.com/wangxiaobao/p/5850949.html

简单说来, 虚函数表干的是这样一件事情: 首先, 为拥有虚函数的类建立(一个或多个, 若是继承链上存在多继承,则可能多个虚函数表)虚函数表。在该类实例化的时候, 为每个对象分配一个虚函数指针, 指向该虚函数表。 虚函数指针放在类所在内存的起始地址处。虚函数表建立之时, 首先复制一份其父类的虚函数表(所以多继承会有多个), 然后检查自己的类的方法中有没有虚函数覆盖了原先的父类的虚函数(我的理解是通过检查函数签名来检查函数是否相同),若有的话把原先虚函数表中指向父类虚函数的地址改为指向自己的那个函数的地址即可。若是存在之前没有在父类虚函数表出现过的虚函数,则在自己的虚函数表中新增一项,并指向自己的这个虚函数。(处于需要增添项来考虑, 个人认为虚函数表在内存不是连续排列,而应该是像链表那样做的)
多继承下具有多个虚函数表, 那么假设子类没有虚函数覆盖任何父类的,而不同父类间存在同名(相同函数签名)的虚函数, 这时候利用父类指针来调用这个名称的虚函数, 具体调用哪个,就要看父类指针的类型了:调用与父类指针的类中的那个函数。
更加具体参考: http://blog.csdn.net/haoel/article/details/1948051/#t5

总结一下: 判断虚函数调用的方法。


  1. 根据父类指针找到子类中相应的虚函数表。(单继承下表唯一)

  2. 判断该虚函数表中该函数名称(签名)对应的函数是否被子类中虚函数覆盖。
    若没被覆盖, 调用父类指针对应类中的函数, 若被覆盖, 调用子类中的对应虚函数。