C++动态类型与静态类型

来源:互联网 发布:网络教育9月几号入学 编辑:程序博客网 时间:2024/05/23 19:56

一、前言

    其实关于C++的动态绑定就足以写好多篇博客了,但在这篇文章中我不准备深刻剖析C++动态绑定机制(即虚函数机制),只是说明动态绑定和静态绑定的区别,强调C++对象的动态类型和静态类型。


二、静态类型与动态类型

    什么是静态类型?什么是动态类型?首先这两种肯定是对象(变量)类型的一种说法,而区分这两种类型最直接的方法就是看表达式的形式。关于一个表达式的对象(变量)的静态类型和动态类型需要从两点进行分析:

        (1)该对象(变量)的静态类型和动态类型是否相等;

        (2)如果该对象的静态类型和动态类型不相等,其动态类型是否有作用。

对于第(1)点,其实我们可以认为每个对象(变量)都存在动态类型和静态类型,只是大多数情况下一个对象的动态类型和静态类型是相等的,所以我们平常都没有强化这两个概念,如表达式int a; 对象a的静态类型和动态类型就是相等的,所以平常我们都是直接称呼变量a的类型是int。对于第(2)点,首先只有当我们的表达式使用的是指针或引用时对象的静态类型和动态类型才有可能不同,而且就算一个对象的静态类型和动态类型不相等,其还不一定能享受到动态类型所带来的方便(即动态绑定),因为我们知道,动态绑定的前提条件是继承体系中的子类继承并重写了基类的虚函数。

    如果觉得上面的解释有点绕,直接解释静态类型和动态类型的定义,一个对象(变量)的静态类型就是其声明类型,如表达式int a中的int就是对象a的声明类型,即静态类型;而一个对象(变量)的动态类型就是指程序执行过程中对象(指针或引用)实际所指对象的类型,如Base* pB = new Drived; 其中class Drived继承于class Base,则指针对象pB的静态类型就是Base(声明类型),动态类型就是Drived(实际所指对象的类型),又如Base* pB = new Base;此时指针对象pB的静态类型和动态类型也是相等的,都是Base。


三、动态类型的作用

    上面已经说明了,大多数情况下一个对象的静态类型和动态类型都是相等的,而且就算其静态类型和动态类型不相等,也不一定会使动态类型发挥威力,那到底什么时候一个对象(变量)的动态类型能发挥其威力呢?三个条件:(1)该对象是指针或引用形式;(2)该对象的静态与动态类型不同;(3)应用场景为带虚函数的继承体系结构。其实你可以直接理解为:动态类型就是为动态绑定(C++继承的多态)准备的。只有当上述3个条件都满足了,动态类型才能发挥其威力,即很好的支持虚函数动态绑定机制,为什么呢?这与C++编译器对多态继承结构中的成员函数调用机制有关。

    假设有以下继承结构:class Drived : public Base,然后有表达式p->mem()或obj.mem(),其中mem()是一个类成员函数,但具体是基类还是子类,现在可以不用管,而且我们也不用管p或者obj是指向哪个类。当程序的编译期,当编译器遇到表达式p->mem()或obj.mem(),执行以下步骤:

        (1)首先确定p(或obj)的静态类型,即声明类型;

        (2)然后在p(或obj)的静态类型中查找mem()函数,如果没有找到,按照作用域规则,这时编译器会到其直接基类中寻找,并依次向上,直到达到继承链的顶端,如果在这个过程中找到了,则继续下一步,如果没有找到,则报错;

        (3)一旦找到名字匹配的mem()函数,则编译器会根据mem()是否是虚函数,以及我们是通过指针或引用(如p)来调用mem()函数,还是通过类对象(如obj)来调用mem()函数,生成不同的代码。如果mem()是虚函数且是通过指针或引用调用(如p->mem()),编译器生成的代码会检查其动态类型,这就是动态绑定的过程,也就是说编译器生成的代码会直到运行期确定了动态类型才决定具体调用基类还是子类的虚函数mem();如果mem()不是虚函数且是通过对象调用(如obj.mem()),则编译器会产生一个常规的函数调用代码,可以直接确定调用哪一个mem()函数。


四、总结

    对上面的介绍进行总结:大多数情况下对象的静态类型和动态类型相同,我们不用关系二者区别;只有多态情况下,即在有虚函数的继承体系中,通过基类的指针或引用调用虚函数时,这时我们就需要关系指针或引用对象的动态类型,因为它们的静态类型毋庸置疑是基类,但动态类型有可能是子类(其实也不是有可能,要想应用多态,其动态类型必须是子类),所以动态类型就是跟虚函数、多态、动态绑定息息相关的。

原创粉丝点击