C++多态及其对象模型

一、多态

所谓多态,就是“多种形态”。

在面向对象的方法中一般是这样描述多态的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。多态性的表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。

1.静态多态

#include <iostream>
using namespace std;

int Add(int x, int y)
{
	return x + y;
}

float Add(float x, float y)
{
	return x + y;
}

int main()
{ 
	cout << Add(2, 3) << endl;
	cout << Add(2.3f, 3.4f) << endl;
	return 0;
}
运行结果如下:

静态多态是通过函数重载实现的。由函数重载和运算符重载形成的多态性属于静态多态,要求编译器在程序编译时就知道调用函数的全部信息。静态多态性又称编译时的多态性。静态多态性的函数调用速度快,效率高,但缺乏灵活性,在程序运行前就已经决定了执行的函数和方法。

2.动态多态

C++中虚函数的主要作用就是实现动态多态。简单说父类的指针/引用调用重写的虚函数,当父类指针/引用指向父类对象时调用的是父类的虚函数,指向子类对象时调用的是子类的虚函数。

虚函数:类的成员函数前面加上virtual关键字,则这个成员函数称为虚函数。

虚函数的重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(覆盖)了父类的这个虚函数。


可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中的不同类的对象,从而调用其中的同名函数。

注意:由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数做出不同的响应。动态多态性的特点是:不在编译时确定是哪个函数,而是在程序运行过程中动态的确定操作所针对的对象,它又称运行时多态性。


虚析构函数:

先看如下代码

程序并没有执行派生类的析构函数。如果希望能够执行派生类的析构函数,可以将基类的析构函数声明为虚函数。

结论:

不加virtual关键字,不构成多态,此时与类型有关,指针p是父类类型,所以只调用父类析构函数。

加上virtual关键字,构成多态,此时与对象有关,会先析构子类对象,再自动调用父类析构函数。


构造函数不能被定义为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上把函数与类对象绑定。
静态成员函数不能是虚函数。因为静态成员函数的特点是不受限制于某个对象。
内联函数不能是虚函数。因为内联函数不能在运行中动态确定位置。

构造函数中不能调用虚函数。虚函数的地址是存在对象里的,调用构造函数之前对象还没初始化,及没有虚函数,更不存在调用可言。

二、多态的对象模型——单继承和多继承

1.单继承

多态中单继承概念:一个子类继承一个父类,子类中对父类的虚函数进行重写,子类继承父类的虚表之后,在此虚表中所对应虚函数存储位置的关系。

用以下例子进行分析:


由上图可知,子类Derive继承父类Base,重写了父类中的虚函数func1(),所以在子类的虚表中可以看到,首先存放的是,子类重写的虚函数func1(),然后是父类的虚函数func2(),子类的虚函数func3(),最后是子类的虚函数func()。在虚表的最后存放了一个0作为结束标志。

2.多继承

多态中多继承的概念:一个子类继承2个或2个以上的父类,子类中对父类的虚函数进行重写,子类会分别继承父类的虚表,同时分别继承的虚表中虚函数的存放位置关系如下图所示:


由上图可知:一个子类Derive继承了两个父类,Base1和Base2. 同时继承了两个虚表,在子类对象中通过内存及打印函数得到,子类中有两个虚表,继承父类Base1的虚表中存放了子类Derive重写的虚函数func1(),继承父类Base1中的虚函数func2(),以及子类Derive中的虚函数func3();另一个继承父类Base2的虚表中存放了子类Derive重写的虚函数func1(),继承父类Base1中的虚函数func2()。同时,虚表的结束标志是0.

三、多态的对象模型--菱形继承和菱形虚拟继承

1.菱形继承

菱形继承是几个类的继承关系呈菱形状。为此,我们举例解释:

有类A,B,C,D。其中B,C继承A,D继承B,C。继承关系图如下:


具体代码如下:

    #include <iostream>  
    using namespace std;  
    class A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"A::f1()"<<endl;  
        }  
        virtual void f2()  
        {  
            cout<<"A::f2()"<<endl;  
        }  
    public:  
        int _a;  
    };  
      
    class B:public A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"B::f1()"<<endl;  
        }  
        virtual void f3()  
        {  
            cout<<"B::f3()"<<endl;  
        }  
    public:  
        int _b;  
      
    };  
      
    class C:public A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"C::f1()"<<endl;  
        }  
        virtual void f4()  
        {  
            cout<<"C::f4()"<<endl;  
        }  
    public:  
        int _c;  
    };  
      
    class D:public B,public C  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"D::f1()"<<endl;  
        }  
        virtual void f5()  
        {  
            cout<<"D::f5()"<<endl;  
        }  
    public:  
        int _d;  
    };  
      
    //打印虚函数表  
    typedef void(*V_FUNC)();  
      
    void PrintVtable(int* vtable)  
    {  
        printf("vtable:%p\n",vtable);  
        int** pvtable=(int**)vtable;  
        for(size_t i=0; pvtable[i]!=0;i++)  
        {  
            printf("vtable[%u]:0x%p->",i,pvtable[i]);  
            V_FUNC f=(V_FUNC)pvtable[i];  
            f();  
        }  
        cout<<"------------------------------------\n";  
    }  
      
    void test()  
    {  
        D d;  
        d.B::_a=5;  
        d.C::_a=6;  
        d._b=1;  
        d._c=2;  
        d._d=3;  
        PrintVtable(*(int**)&d);  
        PrintVtable(*(int**)((char*)&d+sizeof(B)));  
    }  
      
    int main()  
    {  
        test();  
        return 0;  
    }  

创建一个D类的对象d,下图为查看d对象中存储的成员变量情况,以及虚表指向:

解析图:


小结:

在普通的菱形继承中,处于最先的D类型的对象d,它继承了B,C,并且B,C中分别保存了一份来自继承A的变量;除此之外,B,C还存了虚表指针,通过它可以找到虚表中存的虚函数地址,最后,d对象还存放了自己定义的变量和继承B,C自己定义的变量。

菱形继承中,创建一个D类对象d,d中存放了B类的虚表指针及其成员_b、继承A类的_a、C类的虚表指针及其成员_c,继承A类的_a,以及自身的_d。

因为D类中对函数func1()进行了重写,所以在B类的虚表中存放了D类的虚函数func1(),A类的虚函数func2(),B类的虚函数func3(),和D类的虚函数func5();

在C类的虚表中存放了D类的虚函数func1(),A类的虚函数func2(),C类的虚函数func4()。

2.菱形虚拟继承

菱形虚拟继承就是在普通菱形继承的前提下加了虚继承(本例是,B,C虚继承A)。

创建一个D类的对象d,下图为查看d对象中存储的成员变量情况,以及虚表指向:

代码如下:

 #include <iostream>  
    using namespace std;  
    class A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"A::f1()"<<endl;  
        }  
        virtual void f2()  
        {  
            cout<<"A::f2()"<<endl;  
        }  
    public:  
        int _a;  
    };  
      
    class B:virtual public A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"B::f1()"<<endl;  
        }  
        virtual void f3()  
        {  
            cout<<"B::f3()"<<endl;  
        }  
    public:  
        int _b;  
      
    };  
      
    class C: virtual public A  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"C::f1()"<<endl;  
        }  
        virtual void f4()  
        {  
            cout<<"C::f4()"<<endl;  
        }  
    public:  
        int _c;  
    };  
      
    class D:public B,public C  
    {  
    public:  
        virtual void f1()  
        {  
            cout<<"D::f1()"<<endl;  
        }  
        virtual void f5()  
        {  
            cout<<"D::f5()"<<endl;  
        }  
    public:  
        int _d;  
    };  
      
    //打印虚函数表  
    typedef void(*V_FUNC)();  
      
    void PrintVtable(int* vtable)  
    {  
        printf("vtable:%p\n",vtable);  
        int** pvtable=(int**)vtable;  
        for(size_t i=0; pvtable[i]!=0;i++)  
        {  
            printf("vtable[%u]:0x%p->",i,pvtable[i]);  
            V_FUNC f=(V_FUNC)pvtable[i];  
            f();  
        }  
        cout<<"------------------------------------\n";  
    }  
      
    void test()  
    {  
        D d;  
        d.B::_a=5;  
        d.C::_a=6;  
        d._b=1;  
        d._c=2;  
        d._d=3;  
        PrintVtable(*(int**)&d);  
        PrintVtable(*(int**)((char*)&d+sizeof(B)-sizeof(A)));
        PrintVtable(*(int**)((char*)&d+sizeof(D)-sizeof(A)));  
    }  
      
    int main()  
    {  
        test();  
        return 0;  
    }  

解析图:


小结:

菱形虚拟继承与菱形继承的区别在于,B,C继承A的公共成员a,既不存储在B里,也不存储在C里,而是存储在一块公共的部分,而会将B,C相对于这个变量的偏移地址(这里的偏移量地址叫做虚基表)存在B,C里。所以说,对象d里的B,C里存放了虚表指针,虚基表指针,自己的变量。








全部评论

相关推荐

蚂蚁 基架java (n+6)*16 签字费若干
点赞 评论 收藏
分享
joe2333:怀念以前大家拿华为当保底的日子
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务