带有虚函数的类不同方式继承下的对象模型

带有虚函数的单继承

带有虚函数的普通单继承

#include<iostream>
using namespace std;
class Base//定义一个Base类
{
public:
    virtual void BaseTest1()//Base类中定义虚函数BaseTest1()
    {
        cout << "Base::BaseTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void BaseTest2()//Base类中定义虚函数BaseTest2()
    {
        cout << "Base::BaseTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _base;//Base类内定义一个数据成员
};
class Derive : public Base//定义Derive类公有普通继承自Base类
{
public:
    virtual void DeriveTest1()//Derive类中定义函数DeriveTest1()
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//Derive类中定义函数DeriveTest2()
    {
        cout << "Derive::DervieTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//Derive类定义一个数据成员
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base = 1;//通过Dervie类对象改变继承自Base类对象的数据成员
    d._derive = 2;//通过Derive类对象改变它自己的数据成员
    return 0;
}

程序运行结果:
这里写图片描述
通过运行结果可以得知Dervie类对象的大小为12字节,接下来我们就来看看在这12字节内究竟存储了哪些东西?对程序进行调试,并取出Derive类对象所在的内存窗口:
这里写图片描述
我们在程序中执行了d._base=1; d._derive=2;所以0x003BF840就是继承自Base类的数据成员的地址,0x003BF844就是Derive类自己的数据成员的地址。在Derive类对象的前4个字节,存放了一个地址,查看该地址指向的位置:
这里写图片描述
因为Base类与Derive类中一共含有4个虚函数,而该地址指向的位置恰好存放了4个地址,我们可以推测这四个地址就分别是4个虚函数的函数地址,编写一个函数来验证我们的猜想:

typedef void(*PF)();//将函数指针的名字进行重命名

void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*(int*)&d);//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

将这个函数添加在主函数return 0;之前,运行程序,程序运行结果为:
这里写图片描述
程序顺利的执行了Base类和Derive类的虚函数,这也验证了我们的猜想,所以在Dervie类对象的前四个字节的指针,指向一个虚函数表:如果我们通过基类Base类创建对象,则会按照虚函数在类中声明的先后次序依次存放在虚函数表中,如果我们通过派生类Derive类生成对象,则会先将基类中的虚函数表拷贝一份,在下面按照派生类中虚函数的声明先后次序依次将派生类特有的虚函数添加在虚函数表中。
所以可以简单的画出带有虚函数的普通单继承方式下派生类的模型:
这里写图片描述
●如果我们在派生类Dervie类中对基类Base类的某个虚函数进行重写,那么在虚函数表中会怎样存放呢?
针对这个问题,我们对上述程序中的Base类中的BaseTest1()函数在派生类Derive类中进行重写来验证:

#include<iostream>
using namespace std;
class Base//定义一个Base类
{
public:
    virtual void BaseTest1()//Base类中定义虚函数BaseTest1()
    {
        cout << "Base::BaseTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void BaseTest2()//Base类中定义虚函数BaseTest2()
    {
        cout << "Base::BaseTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _base;//Base类内定义一个数据成员
};
class Derive : public Base//定义Derive类公有普通继承自Base类
{
public:
    virtual void BaseTest1()//对Base类的BaseTest1()函数进行重写
    {
        cout << "Derive::BaseTest1()" << endl;
    }
    virtual void DeriveTest1()//Derive类中定义函数DeriveTest1()
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//Derive类中定义函数DeriveTest2()
    {
        cout << "Derive::DervieTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//Derive类定义一个数据成员
};

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*(int*)&d);//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base = 1;//通过Dervie类对象改变继承自Base类对象的数据成员
    d._derive = 2;//通过Derive类对象改变它自己的数据成员
    FunTest(d);
    return 0;
}

程序运行结果:
这里写图片描述
我们可以看到,程序运行依然执行了4个函数,但是基类的Base::BaseTest1()函数已经被覆盖成为重写过的Derive::BaseTest1(),所以就可以得知:如果派生类重写了基类的某个虚函数,就使用派生类重写的虚函数覆盖虚函数表中相同偏移量位置的基类虚函数。
当在派生类中对基类虚函数进行重写时,派生类对象模型:
这里写图片描述


带有虚函数的虚拟单继承
我们依然采用上面的代码,只不过将普通继承替换为虚拟继承:

#include<iostream>
using namespace std;
class Base//定义一个Base类
{
public:
    virtual void BaseTest1()//Base类中定义虚函数BaseTest1()
    {
        cout << "Base::BaseTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void BaseTest2()//Base类中定义虚函数BaseTest2()
    {
        cout << "Base::BaseTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _base;//Base类内定义一个数据成员
};
class Derive :virtual public Base//定义Derive类公有虚拟继承自Base类
{
public:
    virtual void DeriveTest1()//Derive类中定义函数DeriveTest1()
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//Derive类中定义函数DeriveTest2()
    {
        cout << "Derive::DervieTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//Derive类定义一个数据成员
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base = 1;//通过Dervie类对象改变继承自Base类对象的数据成员
    d._derive = 2;//通过Derive类对象改变它自己的数据成员
    return 0;
}

程序运行结果:
这里写图片描述
根据程序的运行结果得知,Dervie类对象的大小为20字节,再来查看这20个字节的内容:
这里写图片描述
因为程序中执行了d._base=1; d._derive=2; 所以0x0077F93C即为继承自Base类的数据成员的地址,0x0077F934就是派生类自己的数据成员的地址,可以发现,虚拟继承比普通继承多出了两个数据,根据我们所学的知识,可以得知这三个地址分别指向:基类成员的偏移量列表,基类的虚函数表,派生类的虚函数表。那我们现在就来查看他们相对应的位置:
先来查看Derive类对象的前4个字节指向的内容:
这里写图片描述
可以看出这时两个函数指针,用下面的函数来执行这两个函数指针指向的函数:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*(int*)&d);//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

运行结果:
这里写图片描述
所以Derive类对象d的前4个字节存放的是派生类虚函数表的地址。
查看0x012BE0AC:
这里写图片描述
可以看出它指向的是基类成员的偏移量列表。偏移量列表前4字节存放的是Derive类对象起始位置相对与偏移量列表起始位置的偏移量,向后四个字节存放基类模型相对与偏移量列表起始位置的偏移量。
查看0x012BDC94:
这里写图片描述
修改代码,让它可以来打印基类虚函数表中的内容:

PFTV = (PF*)(*(int*)&d);//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
//将上面这行代码改为:
PFTV = (PF*)(*((int*)&d + 3));//用来打印基类虚函数表的内容

运行结果:
这里写图片描述
因为在Dervie类中对Base类的BaseTest1()函数进行了重写,所以基类虚函数表中的Base::BaseTest1()函数被Derive::BaseTest1()覆盖。
所以可以画出经过虚拟单继承的派生类对象模型:
这里写图片描述


带有虚函数的多继承

带有虚函数的普通多继承

编写一段代码来验证这个问题:

#include<iostream>
using namespace std;
class Base1//定义一个Base1类
{
public:
    virtual void Base1Test1()//在Base1类中定义第一个虚函数
    {
        cout << "Base1::Base1Test1()" << endl;//函数打印它的作用域及函数名
    }
    virtual void Base1Test2()//在Base2类中定义第二个虚函数
    {
        cout << "Base1::Base1Test2()" << endl;//函数打印它的作用域及函数名
    }
    int _base1;//在Base1类中定义一个数据成员
};
class Base2//定义Base2类
{
public:
    virtual void Base2Test1()//在Base2类中定义第一个虚函数
    {
        cout << "Base2::Base2Test1()" << endl;//函数打印自己的作用域及函数名
    }
    virtual void Base2Test2()//在Base2类中定义第二个虚函数
    {
        cout << "Base2::Base2Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _base2;//在Base2类中定义一个数据成员
};
class Derive :public Base1, public Base2//定义Derive类公有普通继承自Base1和Base2类
{
public:
    virtual void Base1Test1()//在Derive类中对Base1类的第一个虚函数进行重写
    {
        cout << "Derive::Base1Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void Base2Test1()//在Derive类中对Base2类的第一个虚函数进行重写
    {
        cout << "Derive::Base2Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest1()//在Derive类中定义它特有的第一个虚函数
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//在Derive类中定义它特有的第二个虚函数
    {
        cout << "Derive::DervieTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//在Derive类中定义一个数据成员
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印派生类Derive类的大小
    d._base1 = 1;//通过Derive类对象改变继承自Base1类的数据成员
    d._base2 = 2;//通过Derive类对象改变继承自Base2类的数据成员
    d._derive = 3;//通过Derive类对象改变它自己的数据成员
    return 0;
}

运行结果:
这里写图片描述
通过运行结果得知派生类对象的大小为20字节,接着拿出这20个字节的内存窗口,看看它里面究竟存储了哪些数据:
这里写图片描述
因为在程序中执行了d._base1=1; d._base2=2; d._derive=3;所以很容易确定出0x007AFD98即为继承自Base1类的数据成员的地址,0x007AFDA0就是继承自Base2类的数据成员的地址,0x007AFDA4就是Dervie类自己的数据成员的地址。根据经验,我们可以推测出剩下的两个数据分别指向两个虚函数列表,那么他们分别指向谁的虚函数列表呢?编写一段代码来验证:
该函数用来执行Derive类起始的4个字节中的指针指向的虚函数:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*((int*)&d));//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

运行结果:
这里写图片描述
可以看出这是Derive类和Base1类的虚函数表,在执行程序的过程中出现了中断,查找原因发现是因为虚函数表并没用用00 00 00 00 来作为它的结束标志,所以我们得知,用00 00 00 00作为虚函数表结束的标志的情况并不是一定的。
修改代码,让函数执行下面的指针指向的虚函数:

PFTV = (PF*)(*(int*)&d);//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
//将上面这行代码改为:
PFTV = (PF*)(*((int*)&d + 2));//用来打印基类虚函数表的内容

运行结果:
这里写图片描述
有运行结果可知,这个函数指向Base2类的虚函数表。根据分析画出Derive类对象模型;
这里写图片描述


带有虚函数的虚拟多继承
使用同样的代码,将普通继承改为虚拟继承:

#include<iostream>
using namespace std;
class Base1//定义一个Base1类
{
public:
    virtual void Base1Test1()//在Base1类中定义第一个虚函数
    {
        cout << "Base1::Base1Test1()" << endl;//函数打印它的作用域及函数名
    }
    virtual void Base1Test2()//在Base2类中定义第二个虚函数
    {
        cout << "Base1::Base1Test2()" << endl;//函数打印它的作用域及函数名
    }
    int _base1;//在Base1类中定义一个数据成员
};
class Base2//定义Base2类
{
public:
    virtual void Base2Test1()//在Base2类中定义第一个虚函数
    {
        cout << "Base2::Base2Test1()" << endl;//函数打印自己的作用域及函数名
    }
    virtual void Base2Test2()//在Base2类中定义第二个虚函数
    {
        cout << "Base2::Base2Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _base2;//在Base2类中定义一个数据成员
};
class Derive :virtual public Base1,virtual public Base2//定义Derive类公有虚拟继承自Base1和Base2类
{
public:
    virtual void Base1Test1()//在Derive类中对Base1类的第一个虚函数进行重写
    {
        cout << "Derive::Base1Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void Base2Test1()//在Derive类中对Base2类的第一个虚函数进行重写
    {
        cout << "Derive::Base2Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest1()//在Derive类中定义它特有的第一个虚函数
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//在Derive类中定义它特有的第二个虚函数
    {
        cout << "Derive::DervieTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//在Derive类中定义一个数据成员
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印派生类Derive类的大小
    d._base1 = 1;//通过Derive类对象改变继承自Base1类的数据成员
    d._base2 = 2;//通过Derive类对象改变继承自Base2类的数据成员
    d._derive = 3;//通过Derive类对象改变它自己的数据成员
    return 0;
}

运行结果:
这里写图片描述
根据运行结果得知,经过虚拟多继承的派生类(Derive类)对象的大小为28字节,截取出对象所在的内存窗口:
这里写图片描述
因为在程序中执行了d._base1=1; d._base2=2; d._derive=3;所以很容易确定出0x0073FC80即为继承自Base1类的数据成员的地址,0x0073FC88就是继承自Base2类的数据成员的地址,0x0073FC78就是Dervie类自己的数据成员的地址。
查看0x0135DCA8指向的内存窗口:
这里写图片描述
我们推测它应该时虚函数表,并且它没有以00 00 00 00作为结束标志,用我们编写好的程序来测试:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*((int*)&d));//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

运行结果:
这里写图片描述
结果表明,该指针指向的是派生类特有的虚函数表。

查看0x0135DD84指向的内存窗口:
这里写图片描述
根据经验可知,该指针指向偏移量列表,开始的4字节表示派生类对象起始地址相对于偏移量列表起始地址的偏移量-4,向后4字节表示Base1类模型的起始位置相对于偏移量列表起始位置的偏移量8,再向后4字节表示Base2类模型起始位置相对于偏移量列表的起始位置的偏移量16。
查看0x0135E1B4指向的内存窗口:
这里写图片描述
推测该指针为Base1类的虚函数表,编写程序来验证我们的猜想:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*((int*)&d + 3));//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

运行结果:
这里写图片描述
运行结果表明,该地址指向Base1类的虚函数表,并且用00 00 00 00作为虚函数表的结束标志。
查看0x0135DD48指向的内存窗口:
这里写图片描述
推测该地址应该是指向Base2类的虚函数表的地址,编写程序验证:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*((int*)&d + 5));//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

运行结果:
这里写图片描述
结果表明,该地址是Base2类的虚函数表的指针,但是并没有用00 00 00 00作为虚函数表的结束标志。
根据上面的信息,我们可以画出带有虚函数的虚拟多继承派生类对象模型:
这里写图片描述


带有虚函数的菱形继承

带有虚函数的菱形普通继承

#include<iostream>
using namespace std;
class Base//定义Base类
{
public:
    virtual void BaseTest1()//在Base类内定义第一个虚函数
    {
        cout << "Base::BaseTest1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void BaseTest2()//在Base类内打印第二个虚函数
    {
        cout << "Base::BaseTest2()" << endl;//函数打印自己的作用域和函数名
    }
    int _base;//定义Base类的数据成员
};
class C1 : public Base//定义C1类公有普通继承自Base类
{
public:
    virtual void C1Test1()//在C1类内定义第一个虚函数
    {
        cout << "C1::C1Test1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void C1Test2()//在C2类内定义第二个虚函数
    {
        cout << "C1::C1Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _c1;//定义C1类的数据成员
};
class C2 : public Base//定义C2类公有普通继承自Base类
{
public:
    virtual void C2Test1()//在C2类内定义第一个虚函数
    {
        cout << "C2::C2Test1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void C2Test2()//在C2类内定义第二个虚函数
    {
        cout << "C2::C2Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _c2;//定义C2类的数据成员
};
class Derive : public C1, public C2//定义Deive类公有普通继承自C1和C2类
{
public:
    virtual void BaseTest1()//在Derive类中重写Base类的第一个虚函数
    {
        cout << "Derive::BaseTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void C1Test1()//在Derive类中重写C1类的第一个虚函数
    {
        cout << "Derive::C1Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void C2Test1()//在Derive类中重写C2类的第一个虚函数
    {
        cout << "Derive::C2Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest1()//在Derive类中定义它的第一个特有的虚函数
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//在Derive类中定义它的第二个特有的虚函数
    {
        cout << "Derive::DeriveTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//定义Derive类的数据成员
};


int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    //当通过Derive类对象直接访问Base类数据成员时,会导致访问对象不明确
    d.C1::_base = 1;//通过Derive类对象改变继承自C1类的Base类数据成员的值
    d.C2::_base = 2;//通过Derive类对象改变继承自C2类的Base类数据成员的值
    d._c1 = 3;//通过Derive类对象改变继承自C1类的数据成员的值
    d._c2 = 4;//通过Derive类对象改变继承自C2类的数据成员的值
    d._derive = 5;//通过Derive类对象改变Derive类特有的数据成员的值
    return 0;
}

运行结果:
这里写图片描述
由运行结果可知,Derive类对象的大小为28字节,取出Derive类对象所在的内存窗口:
这里写图片描述
通过数值我们可以确定出数据成员的位置,经过分析,剩下的两个数据是指向两个虚函数列表的指针:
用下面的代码来指向虚函数表内的函数:

typedef void(*PF)();//将函数指针的名字进行重命名
void FunTest(Derive& d)//定义一个函数测试函数
{
    PF* PFTV = NULL;//定义一个指向函数指针的指针
    PFTV = (PF*)(*((int*)&d));//将Derive类对象的前四个字节的指针强制类型转换为一个指向函数指针的指针
    while (*PFTV)//设置一个循环,当函数指针保存的值为0时退出
    {
        (*PFTV)();//用指针来执行函数指针指向的函数
        PFTV = (PF*)((int*)PFTV + 1);//执行下一个函数
    }
}

执行0x00CCE1C4指向的虚函数表中的虚函数:
这里写图片描述
通过运行结果可以得知,该指针指向的虚函数表中存放的是Base类(由C1类继承)、C1类、Derive类的虚函数。
执行0x00CCDD70指向的虚函数表中的虚函数:
这里写图片描述
通过运行结果得知,该指针指向的虚函数列表中存放的是Base类(由C2类继承),C2类的虚函数。
所以我们可以画出带有虚函数的菱形普通继承派生类对象模型:
这里写图片描述


带有虚函数的菱形虚拟继承
用相同的代码,更换继承方式为虚拟继承:

#include<iostream>
using namespace std;
class Base//定义Base类
{
public:
    virtual void BaseTest1()//在Base类内定义第一个虚函数
    {
        cout << "Base::BaseTest1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void BaseTest2()//在Base类内打印第二个虚函数
    {
        cout << "Base::BaseTest2()" << endl;//函数打印自己的作用域和函数名
    }
    int _base;//定义Base类的数据成员
};
class C1 :virtual public Base//定义C1类公有普通继承自Base类
{
public:
    virtual void C1Test1()//在C1类内定义第一个虚函数
    {
        cout << "C1::C1Test1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void C1Test2()//在C2类内定义第二个虚函数
    {
        cout << "C1::C1Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _c1;//定义C1类的数据成员
};
class C2 :virtual public Base//定义C2类公有普通继承自Base类
{
public:
    virtual void C2Test1()//在C2类内定义第一个虚函数
    {
        cout << "C2::C2Test1()" << endl;//函数打印自己的作用域和函数名
    }
    virtual void C2Test2()//在C2类内定义第二个虚函数
    {
        cout << "C2::C2Test2()" << endl;//函数打印自己的作用域和函数名
    }
    int _c2;//定义C2类的数据成员
};
class Derive :virtual public C1,virtual public C2//定义Deive类公有普通继承自C1和C2类
{
public:
    virtual void BaseTest1()//在Derive类中重写Base类的第一个虚函数
    {
        cout << "Derive::BaseTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void C1Test1()//在Derive类中重写C1类的第一个虚函数
    {
        cout << "Derive::C1Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void C2Test1()//在Derive类中重写C2类的第一个虚函数
    {
        cout << "Derive::C2Test1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest1()//在Derive类中定义它的第一个特有的虚函数
    {
        cout << "Derive::DeriveTest1()" << endl;//函数打印它的作用域和函数名
    }
    virtual void DeriveTest2()//在Derive类中定义它的第二个特有的虚函数
    {
        cout << "Derive::DeriveTest2()" << endl;//函数打印它的作用域和函数名
    }
    int _derive;//定义Derive类的数据成员
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base = 1;//通过Derive类对象改变继承自Base类的数据成员的值
    d._c1 = 3;//通过Derive类对象改变继承自C1类的数据成员的值
    d._c2 = 4;//通过Derive类对象改变继承自C2类的数据成员的值
    d._derive = 5;//通过Derive类对象改变Derive类特有的数据成员的值
    return 0;
}

运行结果:
这里写图片描述
通过运行结果得知,该继承方式下,派生类(Derive类)的对象大小为44字节,截取出Derive类所在的内存窗口:
这里写图片描述
根据数值可以确定数据成员的位置,接下来分别截取出指针数据所指向的内存窗口:
●查看地址0x0031DD4C:
这里写图片描述
可以看出,该指针指向的是虚函数列表,并且该虚函数列表以00 00 00 00作为虚函数列表结束的标志,我们用代码按次序执行虚函数列表中的虚函数,得到运行结果为:
这里写图片描述
根据结果可以看出,该指针指向的是派生类(Derive类)特有的虚函数列表。
●查看地址0x0031DD88:
这里写图片描述
根据该指针指向的内容可知,该指针指向的是偏移量列表,前4个字节存放派生类(Derive类)对象起始位置相对与指向偏移量列表的指针位置的偏移量-4,先后4字节存放的是基类(Base类)对象模型起始位置相对该指针位置的偏移量8,再向后4字节存放的是C1类对象模型起始位置相对于该指针位置的偏移量16,最后4字节存放的是C2类对象模型起始位置相对该指针位置的偏移量28,所以它是Derive类的偏移量表格。
●查看地址0x0031DD5C:
这里写图片描述
根据指针指向内容看出该指针指向虚函数列表,用代码按顺序执行虚函数列表里的函数,得到结果:
这里写图片描述
通过运行结果得知,该指针指向的是基类(Base类)的虚函数列表,并且Base类的第一个虚函数在Derive类中进行了重写。
●查看地址0x0031DD6C:
这里写图片描述
根据指针指向内容看出该指针指向虚函数列表,用代码按顺序执行虚函数列表里的函数,得到结果:
这里写图片描述
通过运行结果得知,该指针指向的是C1类的虚函数列表,并且C1类的第一个虚函数在Derive类中进行了重写。
●查看地址0x0031DD9C:
这里写图片描述
通过指针指向的内容可知,该指针指向偏移量表格,头4个字节存放它所属的类对象模型的起始位置相对于该指针的位置的偏移量-4,后4个字节存放的是它所属的类所继承的基类(Base类)的对象模型的起始位置相对于该类模型起始位置的偏移量-12,所以该指针指向的是C1类的偏移量列表。
●查看地址0x0031DD7C:
这里写图片描述
可以看出该指针指向的是虚函数列表,用代码按顺序执行它所指向的虚函数列表中的虚函数,得到结果:
这里写图片描述
通过运行结果得知,该指针指向的是C2类的虚函数列表,并且C2类的第一个虚函数在Derive类中进行了重写。
●查看地址0x0031DDA8:
这里写图片描述
通过指针指向的内容可知,该指针指向偏移量表格,头4个字节存放它所属的类对象模型的起始位置相对于该指针的位置的偏移量-4,后4个字节存放的是它所属的类所继承的基类(Base类)的对象模型的起始位置相对于该类模型起始位置的偏移量-24,所以该指针指向的是C2类的偏移量列表。
根据我们的分析,可以画出简单的带有虚函数的菱形虚拟继承派生类对象模型:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/h___q/article/details/81017392
今日推荐