【C++】对象模型

多态&虚函数&对象模型

多态

  • 首先多态建立在继承的前提下,必须有虚函数的重写也称覆盖(父类必须为虚函数,子类可以不是)。
  • 必须通过父类的指针/引用调用虚函数 指向谁就用谁的形态
  • 多态与类型无关,只有对象有关。不同的对象调用相应的虚函数。
    动态绑定:无法确定该函数是什么类型,只有在运行的时候,通过定义的对象所指的类型,才能确定要调用哪个类中的函数
class Person
{
public:
    virtual void B()
    {
        cout << " 0 " << endl;
    }
};
class Student : public Person
{
public:
    virtual void B() //虚函数的重写(函数名 返回值 类型相同)——父类必须虚/子类可以不虚
    {                //父类的指针/引用调用虚函数
        cout << " 1 " << endl;
    }
};
void Fun(Person& p)
{
    p.B();//多态与类型无关,与对象有关
}
int main()
{
    Person p;
    Student s;
    Fun(p);
    Fun(s); 
}

虚函数的使用规则

  • 虚函数的重写要求返回类型函数名参数完全相同。
  • 类外定义不用加virtual,只有声明是加。
  • 构造函数不能是虚函数,最好不要把operator=定义为虚函数。
  • 构造函数析构函数内部不要调用虚函数,会发生未定义的行为。

    构造函数->对象实例化->虚表指针->虚函数 在这个顺序中如果构造函数是虚函数,会是一个悖论。其次构造函数是对象自己调用的,不是通过父类的指针或引用,所以构造函数不能是虚函数

  • 最好把父类的析构函数定义为虚函数。

    基类的析构函数不为虚函数时,会造成内存泄漏,此时只会析构基类,不会调用派生类的析构函数析构派生类。因为虽然基类与派生类的函数名不一样,但编译器还是把他们看作函数的重写(协变)。当通过基类指针指向派生类是只会调用基类的析构函数(静态绑定)无法析构派生类。当定义为虚函数时,相当于多态,会调用派生类的析构函数去析构派生类(动态绑定)。

  • 最后使用虚函数会有虚表指针虚地址表存取操作,所以使用虚函数是有成本,会降低内存运行时间

纯虚函数

基类虚函数的后面加上”=0”,此时基类变成一个抽象类(接口类),抽象类不能实例化出对象,纯虚函数在派生类中重新定义后,派生类可以实例化出对象。所以不同的子类结果不同。
- 友元关系不能继承
- 静态成员可以继承,父子类共用同一个
静态成员/静态成员函数都存放着静态区,作用域为全局,所以共用。

虚表

虚函数在多态性中如此重要,他的存放方式也和普通的成员函数不同,虚函数存放在虚表中。
- 虚表是 用一段连续的内存空间来保存虚函数的地址,通过他能找到虚函数,相当于一个地图。

对象模型

  • 探索单继承对象模型
class A  {
public:
    virtual void fun1() {}
    virtual void fun2() {}
public:  int _a;
};
class B : public A {
public:
    virtual void fun1() {}
    virtual void fun3() {}
public:  int _b;
};
typedef void(*V_FUNC)(); //虚函数的类型重命名为V_FUNC
void PrintVTable(int* vtable)
{
    int* vfArray = (int*)vtable;
    printf("vtable:0x%p\n", vfArray);
    for (size_t i = 0; vfArray[i] != 0; ++i)
    {
        printf("yfunc[%d]:0x%p->", i, vfArray[i]);
        V_FUNC f = (V_FUNC)vfArray[i];
        f();
    }
    cout << endl;
}
int main()
{
    B b;
    b._a = 1;
    b._b = 2;
    A a;
    a._a = 3;
    //PrintVTable((int*)*((int*)&b));
    return 0;
}

B继承了A,对象b里包含了A类,b的虚函数存放在父类A的虚表中,A中的虚表指针指向虚表,虚表包含f1,f2,f3,内存布局如下
这里写图片描述
- 探索多继承对象模型
C继承了A和B,对象c中分别有A和B的俩个虚表,c的虚函数f4存放在A的虚表中,各类的数据成员分别存放在各类中,第一都为虚表指针(存放虚表的首地址)。
这里写图片描述
- 探索菱形虚继承
上篇文章中,我们探索了菱形继承,但没有在各类中加入虚函数,也就是在内存布局中没有存放虚表指针
这里写图片描述
可以看出于多继承不同的是将A类的部分放在了最后,他们共用一个A类,A B C 类都有虚表指针,B C 有虚基表指针,虚基表中存放俩个值,分别是虚基表指针到虚表指针的距离(-4),偏移量。

猜你喜欢

转载自blog.csdn.net/qq_32672481/article/details/76222995