欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多态以及它的单继承、多继承、菱形继承的对象模型

程序员文章站 2022-04-11 15:26:10
...

什么是多态

同一件事物在不同的场景下表现忽的多种形态。不同类的对象对同一消息做出响应,同一消息可以根据发送对象的不同而采用多种不同的行为方式。

静态多态
在编译期间,确定程序的行为(确定具体调用哪个函数)
动态多态
程序运行期间,才能确定程序的运行行为
通常,虚函数是动态绑定,非虚函数是静态绑定,缺省参数值也是静态绑定

  • 实现动态多态的条件:
  1.基类中必须包含虚函数,在派生类中必须对基类中的虚函数进行重写。
       ------例外:
    a>析构函数(函数名不同)
    b>协变:基类虚函数返回基类的引用/指针
            派生类函数返回派生类的引用/指针
   2.通过基类的指针/引用去调用虚函数
  • 那什么是重写呢?

    前提是:在继承体系中
    1.把基类中的某虚函数再在派生类中定义一个一样的虚函数,这个虚函数必须与基类中虚函数原型保持一致。
    2.派生类中函数virtual可以不写,仍旧保持虚函数特性
    3.基类与派生类虚函数的访问权限可以不同,但是基类中虚函数必须是公有的。

  • 什么是同名隐藏

    继承体系中,基类与派生类中具有相同名称的成员(成员变量或成员函数),当通过派生类对象调用相同名称的成员时,优先调用派生类。

多态以及它的单继承、多继承、菱形继承的对象模型
注意:
virtual和static一样只能和函数的声明放在一块。
virtual不能和static放在一块。

多态的原理:

虚函数表:通过一块连续内存来存储虚函数的地址。按照虚函数在基类中的声明次序添加到虚函数表中。

//构造函数里插了一条语句,把虚表的地址放到类的前四个字节,如果没有给构造函数,自动合成一个。
//如果给了,什么也没有做,那编译器就把它改写了。
class Base
{
public:
   Base()
   {}
  virtual void FunTest1()
   {
     cout << "Base::FunTest1()" << endl;
   }
  int _b;
};
int main()
{
   Base b;
   return 0;
}

不同继承下,虚函数的对象模型

  • 1.单继承

    定义这样的基类和派生类:

class Base
{
public:
       Base()
       {}

       virtual void FunTest1()
       {
              cout << "Base::FunTest1()" << endl;
       }
       virtual void FunTest2()
       {
              cout << "Base::FunTest2()" << endl;
       }
       virtual void FunTest3()//加或者删了虚函数,记得重新生成解决方案
       {
              cout << "Base::FunTest3()" << endl;
       }
       int _b;
};
class Derived : public Base
{
public:
       virtual void FunTest5()
       {
              cout << "Derived::FunTest5()" << endl;
       }
       virtual void FunTest2()
       {
              cout << "Derived::FunTest2()" << endl;
       }
       virtual void FunTest4()
       {
              cout << "Derived::FunTest4()" << endl;
       }
       int _d;
};

把虚表打印出来:


typedef void(*PVTF)();//PVTF是void(*)()的别名
void PrintfVTF(Base& b,const string& str)//传基类引用的好处:传派生类和基类的对象都可以打印(赋值兼容规则)
{
       cout << str << endl;
       //先找到对象前四个字节,转换后在解引用,但是解引用后只是一个整数
       PVTF* pVTF = (PVTF*)*(int*)&b;
       while (*pVTF)
       {
              (*pVTF)();//调这个函数
              pVTF++;
       }
       cout << endl;
}
int main()
{
       Base b;
       PrintfVTF(b,"Base VTF:");
       Derived d1;
       Derived d2;
       PrintfVTF(d1, "Derived VTF:");
       return 0;
}

多态以及它的单继承、多继承、菱形继承的对象模型

一个类的多个对象共享同一张虚表:
多态以及它的单继承、多继承、菱形继承的对象模型
对于重写的虚函数和没有重写的虚函数,是如何来调的:
多态以及它的单继承、多继承、菱形继承的对象模型

  • 2.多继承
    首先,我们定义这样的基类和派生类:
    多态以及它的单继承、多继承、菱形继承的对象模型

多态以及它的单继承、多继承、菱形继承的对象模型
对于单继承来说,在派生类里没有重写的函数是在基类的虚表后面,那对于多继承,派生类里没有重写的函数是在哪个基类的虚表后面呢?
那我们把两个基类的虚函数表打印出来看看:

多态以及它的单继承、多继承、菱形继承的对象模型
3.菱形继承
同样,我们声明这样的类:
多态以及它的单继承、多继承、菱形继承的对象模型

对于菱形继承存在二义性问题,那我们把这个多态里的菱形继承让它虚拟继承,来看看对象模型:
多态以及它的单继承、多继承、菱形继承的对象模型

现在我们来看,这连个地址哪个是虚表地址,哪个是偏移量表格地址:
多态以及它的单继承、多继承、菱形继承的对象模型

所以,多态中的虚拟继承里,第一个地址是虚表地址,第二个地址是虚拟继承里偏移量表格的地址。