C++多态及多态对象模型

首先,讲到多态就得讲一下虚函数,在上一篇讲菱形继承时,我们提到一个概念虚继承,那么我们会联想到虚继承和虚函数是否有关系?,其实呢虚函数和虚继承是没有关系的只不过是关键字一样(virtual)。

1.虚函数,类的成员函数前加virtual关键字,此函数成为虚函数。
2虚函数重写,当子类重定义了与基类完全相同的函数时,则成为虚函数重写(也称覆盖)了父类的虚函数。

多态:当基类对象的指针、引用调用子类重写的虚函数时,当指向父类时调用的就是父类的虚函数,当指针指向子类时调用的就是子类重写的虚函数。(与类型无关,与对象有关);

一.讲到多态,就先来看一下虚函数的重写:
class Base
{
public:
    virtual void f1()
    {
        cout << "Base:: f1()" << endl;
    }
    virtual void f2()
    {
        cout << "Base:: f2()" << endl;
    }
public:
    int _a = 1;
};
class Derive:public Base
{
public:
    void f1()//子类重写(覆盖)了父类的虚函数
    {
        cout << "Dirve:: f1()" << endl;
    }
    virtual void f3()
    {
        cout << "Dirve:: f2()" << endl;
    }
public:
    int _b = 2;
};
void test()
{
    Derive d;
    Base b;
    Base* p = &b;
    p = &d;//将子类对象赋给父类指针(引用);
}

这里写图片描述这里写图片描述

②多态:构成多态的条件 1.虚函数 2.虚函数的重写 3.将子类对象赋给父类的指针(引用)4.父类指针调用虚函数;或者说是(动态联编+虚函数重写)
动态联编即就是指针调用的函数需要在(运行时)在虚表中找,运行时决议;
二.多态:


void test()
{
Derive d;
Base b;
Base* p = &b;//父类的指针
p = &d;//将子类对象赋给父类指针(引用);
p->f1();//指针调用f1()虚函数函数
}
这里写图片描述

通过运行结果可以看到 p指针(父类的指针)调用到的是子类重写的虚函数,会看到父类指针调用到了子类对象的虚函数(这样就避免了指示不明确的问题,即多态),那么问题又来了,在子类中这是怎实现的?
监视窗口p的结构模型:

这里写图片描述
这里写图片描述

除了监视窗口的情况我们还得看一下内存中的对象模型:

当父类指针指向子类对象时,我们会发现子类的的虚函数f3()(除重写的虚函数)在监视窗口中看不到!!对于这种情况,我们就得采用特殊手段了(打印虚表)

typedef void(*Funp)( );//函数指针
void print_table(int* v_table)//打印虚表
{
    cout << "====================================" << endl;
    int i = 0;
    Funp f = NULL;
    while (v_table[i]!=0)
    {
        printf("v_table[%d]:%p->",i, v_table[i]);
        f = (Funp)v_table[i];
        f();
        i++;
    }
    cout << "====================================" << endl;
}
void test()
{
    Derive d;
    Base b;
    Base* p = &b;
    p = &d;//将子类对象赋给父类指针(引用);
    p->f1();
    int* v_table = (int*)(*(int*)&b);
    print_table(v_table);//打印虚表
    v_table = (int*)(*(int*)&d);
    print_table(v_table);

}

这里写图片描述

最后一个问题来了,当我们写的程序需要在不同平台和环境中跑,当在64位环境下时这打印虚表的程序还能跑吗?验证一下:

当我把环境从32位变成64位时,这个程序挂了(原因在于v_table = (int*)((int)&d),v_table得到的是虚表指针的地址,而((int)&d)是一个int的大小在64位和32位下都是4个字节),但在32位环境中可以看一个指针的内容,而在64位下就不够看一个指针空间的内容)。为了解决这个问题我们采用二级指针来解决,这样利用指针在32位系统下是4个字节在64位下是8个字节的特点,即指针特性来完成:

“`
typedef void(*Funp)( );//函数指针
void print_table(int** v_table)//打印虚表
{
cout << “====================================” << endl;
int i = 0;
Funp f = NULL;
while (v_table[i]!=0)
{
printf(“v_table[%d]:%p->”,i, v_table[i]);
f = (Funp)v_table[i];
f();
i++;
}
cout << “====================================” << endl;
}
void test()
{
Derive d;
Base b;
Base* p = &b;
p = &d;//将子类对象赋给父类指针(引用);
p->f1();
int** v_table = (int**)((int*)&b);
print_table(v_table);//打印虚表
v_table = (int**)((int*)&d);
print_table(v_table);
}

“`这里写图片描述

猜你喜欢

转载自blog.csdn.net/fangxiaxin/article/details/78483228