C++多态(virtual)(2)

来源:互联网 发布:win10网络连接是空的 编辑:程序博客网 时间:2024/05/19 12:35

前面我们简单的了解了多态,多态的概念,分类,实现条件,基本原理等,也通过上厕所的实例来具体阐述了多态,详情见http://blog.csdn.net/dai_wen/article/details/78537167
那么,现在,就多态在在做如下深度解剖:

1,多态的概念:

多态:顾名思义,是指同一事物在不同情场景下展现出不同的形态。

2,多态实现的条件:

(1),基类中必须有虚函数,在派生类中对基类的虚函数进行重写;
(2),通过基类对象的指针或引用调用虚函数。

3,多态的分类:

多态分为静态多态(早绑定)和动态多态(晚绑定)。

(1)静态多态:

静态多态:在程序编译阶段进行绑定动作,有函数重载和泛型编程。

(2)动态多态:

动态多态:在程序运行时进行绑定动作。
举个静态多态的栗子:

#include<stdio.h>int Add(int x,int y){    return x+y;}double Add(double x,double y){    return x+y;}char Add(char x,char y){    return x+y;}int main(){    int x=0;    double y=0;    char z=0;    x=Add(10,20);    y=Add(10.0,20.0);    z=Add('1','2');    printf("x=%d\n",x);    printf("y=%lf\n",y);    printf("z=%c\n",z);     return 0;}

程序运行如下:
图一

在编译时:编译器通过所传参数的类型不同,选择调用不同的函数,即使函数名相同,函数体也相同,并不会造成调用混乱现象,这种方式是通过参数推演,确定类型。

4,虚函数:

(1)纯虚函数:

纯虚函数是在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。

(1)抽象类:

(1)包含纯虚函数的类叫做抽象类(也叫接口类);
(2)在派生类中对基类的纯虚函数进行实现,那么该派生类也是一个抽象类。

(2)特别说明:

(1) 抽象类中的纯虚函数仅有函数的声明,故而是不完整的,所以不能实例化对象。
只有当纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
(2)虽然不能实例化创建对象,但是可以创建一个指针,不过,这样一般意义不大。

(2)虚函数的内存:

(1)举个栗子:

(1)没有任何成员变量的类,其中也不含虚函数

//计算一个类的大小:将类中所有的非静态成员变量加起来即可,注意内存对齐class base{public :    void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }};int main(){    cout<<sizeof(base)<<endl;    return 0;}//在该类中,只有成员函数,没有任何成员变量,故而该类所占的内存大小为1个字节

图二

(2)没有任何成员变量的类,含有虚函数

class base//**********************************内存大小为4+4个字节{public :    virtual void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    /*virtual*/ void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    /*virtual*/ void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }};int main(){    cout<<sizeof(base)<<endl;    return 0;}

图三
如上图所示,在类中的成员函数之前若加上virtual关键字,使其变为虚函数,那么,该类在内存中所占的内存大小为4个字节,与virtual关键字的个数无关,但每加一个虚函数在内存中便多一个地址。

(3)虚函数的存储方式:

#include<iostream>using namespace std;class base//**********************************内存大小为4+4个字节{public :    virtual void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    virtual void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    virtual void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }public:     int pubd;};//void (*FunTest2)();//函数指针,没有返回值也没有参数的函数指针,FunTest2是函数指针变量typedef void (*PVTF)();//加上关键字typedef ,使其成为函数指针类型int main(){    cout<<sizeof(base)<<endl;    base b;    b.pubd =1;    //*(int*)&b;//找到空间    //FunTest2 f;    PVTF* pfun=(PVTF*)*(int*)&b;    while(*pfun)//打印    {        (*pfun)();        pfun++;    }    return 0;}

图四
当类中存在虚函数同时也有自己的成员变量时, 那么,在内存中,它的存储方式是怎样的? 先存指向虚函数列表的四个字节还是先存类中自己的成员函数?实际上,在内存中,存储方式如下:
图五

类base中自身成员变量存在下,虚表指针4字节在首。

(4)虚函数表:

既然上述例子是将类中的虚函数放在类自身成员函数之前,所以,得出虚函数所占内存位于自身成员之上,这是不是偶然呢?下面我们来验证:将类自身成员写在前,虚函数写在后,验证上述结果是否偶然现象:

class ba//基类{public :    virtual void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    virtual void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    virtual void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }public:     int pubd;};class derived :public base//derived共有继承base{public ://将派生类中自身的虚函数放在基类之前    virtual void FunTest4()    {        cout<<"derived::FunTest4()"<<endl;    }    virtual void FunTest5()    {        cout<<"derived::FunTest5()"<<endl;    }    virtual void FunTest1()    {        cout<<"derived::FunTest1()"<<endl;    }    //将派生类自己的虚函数放在后面    /*    virtual void FunTest4()    {        cout<<"derived::FunTest4()"<<endl;    }    virtual void FunTest5()    {        cout<<"derived::FunTest5()"<<endl;    }*/public:    int pubd;};//void (*FunTest2)();//函数指针,没有返回值也没有参数的函数指针,FunTest2是函数指针变量typedef void (*PVTF)();//加上关键字typedef ,使FunTest2成为函数指针类型//打印基类和派生类的对象void PrintVFT(base &b,char* str){    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}int main(){    cout<<sizeof(base)<<endl;    cout<<sizeof(derived)<<endl;    base b;    derived d;    PrintVFT(b,"Base VFT:");    PrintVFT(d,"derived VFT:");    return 0;    }

图六

由此可见,内存中虚函数所占内存位于类自身成员之上,这并非偶然。

(1)基类中:

在基类中,虚函数的排列顺序按在基类中虚函数声明次序排列;

(2)派生类中:

派生类中虚函数的次序:先基类,在派生类自身成员变量,即:积基类在前,派生类在后,于派生类自身成员变量前后无关,但派生类中的成员变量在虚函数表中的次序按其在派生类中声明的次序;

5,三种继承的虚函数表:

1,单继承:

(1)基类虚函数表:

虚函数按照在类中的声明次序在虚函数表中依次排列。

(2)派生类虚函数表:

(1),先将基类中虚函数表拷贝一份;
(2),如果派生类对基类的虚函数进行重写,则使用派生类的虚函数替换相同偏移量位置的基类虚函数;
(3),如果派生类中新增加自身的虚函数,此类虚函数按照其在派生类中的先后声明次序放在基类虚表之后;

(3),单继承模型:

图六

(4),单继承实例:

class ba//基类{public :    virtual void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    virtual void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    virtual void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }public:     int pubd;};class derived :public base//derived共有继承base{public :    virtual void FunTest1()    {        cout<<"derived::FunTest1()"<<endl;    }    virtual void FunTest4()//将派生类自己的虚函数放在后面    {        cout<<"derived::FunTest4()"<<endl;    }    virtual void FunTest5()    {        cout<<"derived::FunTest5()"<<endl;    }public:    int pubd;};typedef void (*PVTF)();//打印基类和派生类的对象void PrintVFT(base &b,char* str){    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}int main(){    cout<<sizeof(base)<<endl;    cout<<sizeof(derived)<<endl;    base b;    derived d;    PrintVFT(b,"Base VFT:");    PrintVFT(d,"derived VFT:");    return 0;    }

2,多继承:

(1)多继承程序如下:

class B1{public :    virtual void FunTest1()    {        cout<<"base::FunTest1()"<<endl;    }    virtual void FunTest2()    {        cout<<"base::FunTest2()"<<endl;    }    virtual void FunTest3()    {        cout<<"base::FunTest3()"<<endl;    }};class B2//**********************************内存大小为4+4个字节{public :    virtual void FunTest4()    {        cout<<"base::FunTest4()"<<endl;    }    virtual void FunTest5()    {        cout<<"base::FunTest5()"<<endl;    }    virtual void FunTest6()    {        cout<<"base::FunTest6()"<<endl;    }   };class derived :public B1,public B2{public :    virtual void FunTest2()    {        cout<<"derived::FunTest2()"<<endl;    }    virtual void FunTest6()    {        cout<<"derived::FunTest6()"<<endl;    }    virtual void FunTest7()    {        cout<<"derived::FunTest7()"<<endl;    }    int _d;};typedef void (*PVTF)();void PrintVFT(B1& b,char* str)//打印B1{    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}void PrintVFT(B2& b,char* str)//打印B2 {    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}int main(){    derived d;    d._d=1;    B1& b1=d;    B2& b2=d;    PrintVFT(b1,"B1 VFT");    PrintVFT(b2,"B2 VFT");        return 0;}

B1 拥有三个虚函数,分别为FunTest1()、FunTest2()、FunTest3();B2 也拥有三个虚函数,分别为FunTest4()、FunTest5()、FunTest6();让derived共有继承B1、B2 ,同时在该派生类中对FunTest2()和FunTest6()进行重写,在创建自身虚函数FunTest7();程序运行结果如下:
图七

(2)对象模型:

图八

(3) 由上小结:

多继承拥有上述单继承中的前两步,(1),先将基类中虚函数表拷贝一份;
(2),如果派生类对基类的虚函数进行重写,则使用派生类的虚函数替换相同偏移量位置的基类虚函数;不同的是:派生类自身的成员虚函数将放在第一个共有继承的基类虚函数列表后,如上述程序运行结果所示,FunTest7()放在B1 之后;

(3)菱形继承:

(1)菱形继承模型:

图

(2)菱形继承程序:

class B{public :    virtual void FunTest1()    {        cout<<"B::FunTest1()"<<endl;    }    virtual void FunTest2()    {        cout<<"B::FunTest2()"<<endl;    }    int _b;};class C1:public B{public :    virtual void FunTest1()    {        cout<<"C1::FunTest1()"<<endl;    }    virtual void FunTest3()    {        cout<<"C1::FunTest3()"<<endl;    }    int _c1;};class C2:public B{public :    virtual void FunTest2()    {        cout<<"C2::FunTest2()"<<endl;    }    virtual void FunTest4()    {        cout<<"C2::FunTest4()"<<endl;    }    int _c2;};class derived :public C1,public C2{public :    virtual void FunTest1()    {        cout<<"derived::FunTest1()"<<endl;    }    virtual void FunTest3()    {        cout<<"derived::FunTest3()"<<endl;    }    virtual void FunTest4()    {        cout<<"derived::FunTest4()"<<endl;    }    virtual void FunTest5()    {        cout<<"derived::FunTest5()"<<endl;    }    int _d;};typedef void (*PVTF)();void PrintVFT(C1& b,char* str)//打印B1{    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}void PrintVFT(C2& b,char* str)//打印B2 {    PVTF* pfun=(PVTF*)*(int*)&b;        while(*pfun)        {            (*pfun)();            pfun++;        }        cout<<endl;}int main(){    cout<<sizeof(derived)<<endl;    derived d;    C1& c1=d;    PrintVFT(c1,"C1 VFT");        C2& c2=d;    PrintVFT(c2,"C2 VFT");        return 0;}

(3)菱形继承程序运行结果:

这里写图片描述

6,总结:

本文深入浅出的方式引出了虚函数在三种不同的继承中,在内存中所占字节大小,以及存储方式和在内存中如何排列;通过给出模型,以程序实例的方式直观的展示,下篇文章中,将继续讲解多态中带虚函数的虚拟继承;望指出不足之处,共同进步。