C++ 多态/多态对象模型 单继承 多继承 菱形继承

1:什么是多态?

多态=动态多态+静态多态
a.关于静态多态:函数重载
b.关于动态多态:
构成动态多态的两个必要条件:
(1)子类对父类的虚函数重写
(2)函数通过父类得到指针或引用进行传参 
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。 

举个例子

//多态
class Person {
public:
    virtual void BuyTickets() {               //父类函数定义为虚函数
        cout << "买全票" << endl;
    }
};
class Student : public Person {
public:
    virtual void BuyTickets() {           //子类重写父类的虚函数
        cout << "买半价票" << endl;
    }
};
void Fun(Person& p) {                     //形参为父类的引用
    p.BuyTickets();                       //构成多态条件,p调用的函数与p
                                          //的类型无关,与传给p的实参对象
                                          //的类型有关
}
int main(void) {
    Person p;                             //定义父类对象
    Student s;                            //定义子类对象
    Fun(p);                               //传父类对象,调用父类的函数
    Fun(s);                               //传子类的对象,调用子类的函数
    return 0;
}

这里写图片描述
总结:

1、派生类重写基类的虚函数视为多态,要求函数名,参数列表,返回值完全相同(协变除外,稍后解释协变)
2、基类定义了虚函数,派生类中继承这一特性。注意:如果去掉基类的该函数前的virtual,则不会构成多态,只去掉派生类中该函数前的virtual,依然构成多态,派生类默认继承virtual。
3、只有类的成员函数才能定义为虚函数,所以虚函数一定是类的成员函数。
4、静态的成员函数不能定义为虚函数,因为没有this指针。
5、在类外面定义函数时,virtual只能加在函数声明的地方,不能加在类外面定义函数的地方。
6、构造函数不能定义为虚函数,构造函数是在对象创建时调用的。
7、不要在构造函数和析构函数中调用虚函数,在这两个函数中,对象可能是不完整的,会出现未定义的场景,因为子类的拷贝构造是合成的,需要调用父类的拷贝构造,但若此时其中定义了虚函数,是根据对象来调用函数的,原来想子类切片给父类,却调用了子类的虚函数,此时子类对象还未初始化完成。
8、最好把基类的析构函数声明为虚函数。

2:单继承
当子类与父类构成多态时,子类继承父类的虚表之后,子类虚表里虚函数在内存里的存储情况。
这里写图片描述
Base类中虚函数fun1,fun2的地址,第二个虚表是Drive类继承父类的虚表,明显这个虚表的内容发生了变化,分别是Drive类中重写父类的虚函数fun1,继承父类的虚函数fun2,以及子类本身的虚函数fun3,fun4,需要注意的是,每个虚表都以0结束。
3:多继承
当一个子类继承多个父类时构成多继承,此时子类会继承这些父类的虚表,子类虚表里虚函数在内存里的存储情况。

这里写图片描述
4:由多继承引发的菱形继承
(1)、普通菱形继承
这里写图片描述

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,它继承了B,C,并且B,C中分别保存了一份来自继承A的变量;除此之外,B,C还存了虚表指针,通过它可以找到虚表中存的虚函数地址,最后,d对象还存放了自己定义的变量和继承B,C自己定义的变量。
(2)、菱形虚拟继承
菱形虚拟继承就是在普通菱形继承的前提下加了虚继承(B,C虚继承A)创建一个D类的对象d,下图为查看d对象中存储的成员变量情况,以及虚表指向:
这里写图片描述
总结:菱形虚拟继承与菱形继承的区别在于,B,C继承A的公共成员a,既不存储在
B里,也不存储在C里,而是存储在一块公共的部分,而会将B,C相对于这个变量的
偏移地址(这里的偏移量地址叫做虚基表)存在B,C里。所以说,对象d里的B,C里存放了虚表指针、虚基表指针、自己的变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值