c++的多态和虚函数(一)

前言:   

       提到c++的多态,很容易就会想到通过继承和虚函数,让同一函数的调用表现出不同的结果。其实C++支持多种形式的多态,从绑定时间来看可以分成静态多态和动态多态,也称为编译期多态和运行期多态。

       其中,动态多态,通过继承和虚函数机制实现;静态多态,在c++中则主要体现为模板(类模板和函数模板)和重载(函数重载和运算符重载)。其实说到静态多态,突然想到,在C里面的宏也可以看做是一种多态(个人观点),还有我们熟悉的printf函数,虽然不是面向对象的多态。说到宏,扯一句,突然知道<stdio.h>的getchar和putchar以及<ctype.h>的tolower等“函数”,居然一般都是宏,很是震惊,居然为了避免函数调用的开销,用宏提高效率,不过不得不服啊,这些库函数设计者的智慧。另外,c++的内联函数,感觉也是宏的思想(会不会内联是因为类内部不支持宏而产生的呢?貌似不是)

 一、向上转换和地址

         向上映射,官方定义是,取一个对象的地址(或指针或引用),并看作基类的地址。由于继承树是以基类为顶点的,对象可以作为它自己的类型或它的基类的对象使用。另外,还能通过基类的地址被操作。

        需要注意的就是向上映射,仅处理地址。纯虚函数还能防止对纯抽象类的函数以传值方式调用。这样,它也是防止对象意外使用值向上映射的一种方法。这样就能保证在向上映射期间总是使用指针或引用。

        为什么传地址而一般不是传值呢,因为地址大小相同,哈哈,这样传基类和派生类地址就一样了。说到底,多态的目的,还是是让对基类对象操作的代码也能操作派生类对象。值向上映射引发对象切片什么的,就不说了,悲剧啊。

二、晚捆绑(感觉这翻译忒别扭,late binding)

        绑定,官方定义,是将函数体与函数调用相关联。当绑定发生在程序运行前时,叫早绑定, 由编译器和连接器完成(C编译只有一种函数调用)。晚绑定,高级货,通过关键字v i r t u a l告诉编译器,先别绑定。当然,编译器得支持晚绑定机制了。

        为了晚绑定呢,编译器就对每个包含虚函数的类创建一个表(称为V TA B L E)。在V TA B L E中,编译器放置特定类的虚函数地址。在每个带有虚函数的类中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E。通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置V TA B L E、初始化V P T R、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。利用虚函数,这个对象的合适的函数就能被调用,哪怕在编译器还不知道这个对象的特定类型的情况下。

        当然,测试VPTR的大小可以发现,为4(32位win7,vc6和MINGW测试),这是因为V P T R指向一个存放地址的表,只需要一个指针。(其实我很好奇的是这个存地址的表存在哪里)

        虚表什么的,太长,下回再搞

三、虚函数与构造函数

        首先,我们提出个问题,构造函数可以为虚函数吗?

       当创建一个包含有虚函数的对象时,必须初始化它的V P T R以指向相应的V TA B L E。这必须在有关虚函数的任何调用之前完成。因为构造函数有使对象存在的工作,所以它也必须设置V P T R。编译器在构造函数的开头部分秘密地插入能初始化V P T R的代码。事实上,即使我们没有对一个类创建构造函数,在有虚函数的情况下,编译器也会为我们创建一个带有相应V P T R初始化代码的构造函数。

       如果我们正在构造函数中并且调用虚函数,那么会发生什么现象呢?

       对于普通的成员函数,虚函数的调用是在运行时决定的,这是因为编译时并不能知道这个对象是属于这个成员函数所在的那个类,还是属于由它派生出来的类。但是,对于在构造函数中调用一个虚函数的情况,被调用的只是这个函数的本地版本

       why?一个原因是虚函数能调用在派生类中的函数,如果我们在构造函数中也这样做,那么我们所调用的函数可能操作还没有被初始化的成员,这将导致灾难的发生。另一个原因是,当一个构造函数被调用时,它做的首要的事情之一是初始化它的V P T R。

       当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码- -既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的V P T R必须是对于这个类的V TA B L E。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内, V P T R将保持被初始化为指向这个V TA B L E。但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置V P T R指向它的V TA B L E,等等,直到最后的构造函数结束(其实就是继承过程中,构造函数调用顺序的问题)。V P T R的状态是由被最后调用的构造函数确定的,这也是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。

      一个问题就是,如果子类没有重写构造函数,那么这个VPTR指向的还是基类的VTABLE,这不是bug吗

      那,析构函数能为虚函数吗? 

      虽然析构函数象构造函数一样,是“例外”函数,但析构函数可以是虚的,这是因为这个对象已经知道它是什么类型(而在构造期间则不然)。一旦对象已被构造,它的V P T R就已被初始化了,所以虚函数调用能发生。虽然书上有云:但析构函数能够且常常必须是虚的,不过目前还遇不到,暂且就不记了吧


     目前先总结这些吧,大多是参考 C++编程思想,记录以备不忘,希望有用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值