C++三大特性——多态

来源:互联网 发布:西安传智java培训 编辑:程序博客网 时间:2024/05/17 02:07

1、什么是多态?

多态:从名字上看就是多种状态,形态,
举个简单的例子: 一个操作符 :&

当&放在一个变量前面,就是区地址操作符;放在类型后面,一般就是引用了。

多态分为两种:

  • 静态多态:在程序编译期间就确定了形态,例如:函数重载,模板
  • 动态多态:在函数执行时才能知道怎么执行

2、多态的实现条件

多态实现的条件有两个:

1、重写:基类一定是虚函数,函数一个在基类,一个在派生类,相同的函数原型(函数名、参数、返回值类型)
特例:协变:两个函数的返回值是各自类的指针或者引用。
2、用基类的指针/引用指向派生类,再用派生类调用这些虚函数,就会产生多态。

3、几种多态模型

1、包含虚函数的类

  • 代码:
class B{public:    virtual void FunTest1()    {        cout<<"FunTest1()"<<endl;    }    virtual void FunTest2()    {}    int _b;};//sizeof(B) = 4 + 4 = 8;
  • 模型:
    这里写图片描述

2、实现多态的单继承

class B{public:    virtual void FunTest1()    {        cout<<"B::FunTest1()"<<endl;    }    virtual void funTest2(int a)    {        cout<<"B::FunTest2()"<<endl;    }    virtual void funTest3()    {        cout<<"B::FunTest2()"<<endl;    }    void funTest4()    {        cout<<"B::FunTest2()"<<endl;    }    int _b;};class D:public B{public:    virtual void FunTest1()//构成重写    {        cout<<"D::FunTest1()"<<endl;    }    virtual void funTest2(char c)//没有构成重写,函数原型不同(参数类型不同)    {        cout<<"B::FunTest2()"<<endl;    }    void funTest3()//构成重写:在基类中是虚函数    {        cout<<"B::FunTest2()"<<endl;    }    virtual void funTest4()//没有构成重写,在基类中不是虚函数    {        cout<<"B::FunTest2()"<<endl;    }    int _b;};

这里写图片描述
- 可以看出虚函数是通过虚函数地址调用的,所以在我们用基类的引用指向派生类时,有可能产生多态,我们来做一个测试:

int main(){    D d;    B &b = d;    b.FunTest1();// 形成多态,通过虚函数表调用D::FunTest()    b.FunTest2(1);//没有形成多态,直接掉用B::FunTest2()    b.FunTest3();//形成多态,通过虚函数表调用FunTest3()    b.FunTest4();//直接调用B::FunTest4()    //b.FunTest    return 0;}

结果打印:
这里写图片描述

3、含虚函数的多继承

这里写图片描述

4、含虚函数的菱形继承

这与普通菱形继承相同,也存在二义性问题,所以又要用到虚拟继承:

5、含虚函数的虚拟继承

这里写图片描述
解释:在偏移量表中有两个数(都是偏移量):

第二个很好理解是派生类对象继承的基类内容存储的位置相对于派生类对象首地址的偏移量。
第一个:我们可以理解为派生类对象首地址相对于该偏移量指针地址的偏移量
ps:上面这个模型是派生类么有写构造函数,当我们写构造函数/析构函数时会在派生类对象和派生类对象继承的基类之间加上4个字节存储着00 00 00 00,这里可以理解为分割开派生类成员和虚拟继承成员

5、含虚函数的菱形虚拟继承

这里我想表明:我们先把菱形继承分成3层,第一层:基层,第二层:派生中层,第三层:派生底层;这里的第三层不能对基层的虚函数进行重写。
这里写图片描述
这种模型的继承是存在问题的:当我们用D实例化一个对象d,并用d.FunTest1()

本在继承C2中没有重写,在C1中重写了,所以调用时到底该调用谁呢?
我的图中已经标了出来,调用FunTest1,则会通过虚函数表找到C1的FunTest2()调用,所以调用的时C1::FunTest1(), FunTest2()也有同样的问题。
如果我们想调用B::FunTest1(),或者B::FUnTest2(),则需要加上d.B::FunTest1(),直接通过域进行调用。

原创粉丝点击