zoukankan      html  css  js  c++  java
  • c++,虚函数,单继承,多继承虚表剖析

    C++的多态性体现在两个方面,一个函数重载,一个虚函数,重载的多态性是在编译器编译期的时候早已经决定了。编译器实现函数重载的时候,也就是进行了名称粉碎。

    而虚函数则是运行期的多态。多态有什么用? 可能你会有此疑惑。最普遍的说法是“提高代码的重用性”。

    如果大家对逆向感兴趣的话,虚函数的内存结构那是必须得掌握的,下面我们来慢慢剖析类中虚函数的内存结构。

    class Ca
    {
    public:
      Ca()
      {
      }
      virtual ~Ca()
      {
      }
      virtual void Fun1()
      {
      }
      virtual void Fun2()
      {
      }
      void Fun3()
      {
      }
    };
    
    class Cb : public Ca
    {
    public:
      virtual void Fun1()
      {
      }
    };

    现在我们讨论单继承的虚表情况。首先我们定义2个对象:

    int main(int argc, char* argv[])
    {
      Ca theA;
      Cb theB;
     
      return 0;
    }

    F10单步调试,虚表情况如下:

    Ca虚表如下:

    Ca::~Ca   Ca::Fun1  Ca::Fun2

    Cb的虚表呢?

    首先拷贝一份父类的虚表,然后在把自己的与父类同名的虚函数覆盖上去,则是子类的虚表

    Ca::~Ca   Ca::Fun1  Ca::Fun2

    最后Cb的虚表如下:

    Cb::~Cb   Cb::Fun1  Ca::Fun2

    前提是子类必须有同名虚函数,则覆盖上去,这也就是为什么叫覆盖,而不叫隐藏的道理。

    下面我们在来剖析多继承的虚表会是什么样的情况。

    class Ca
    {
    public:
      Ca()
      {
      }
      virtual ~Ca()
      {
      }
      virtual void Fun1()
      {
      }
      virtual void Fun2()
      {
      }
    };
    
    class Cb
    {
    public:
      virtual void Fun1()
      {
      }
      virtual void Fun2()
      {
      }
    };
    
    class Cc : public Ca, public Cb
    {
    public:
      virtual void Fun1()
      {
      }
    };
    int main(int argc, char* argv[])
    {
      Ca theA;
      Cb theB;
      Cc theC;
    
      theC.Fun1();
    
      return 0;
    }

    看了前面单继承的剖析,我们来写出Ca和Cb的虚表

    Ca::~Ca  Ca:Fun1  Ca::Fun2

    Cb::Fun1  Ca::Fun2

    Cc的虚表呢?

    前面已经说过,先拷贝父类的虚表,然后如果子类存在和父类同名的虚函数,则覆盖上去。

    编译器该怎么覆盖?

    我们先来看看虚表的安排情况:

    可以看出。对应内存关系如下:

    编译器按照声明类的前后关系,依次拷贝虚表。

    先拷贝:

    Ca::~Ca  Ca:Fun1  Ca::Fun2

    在依次在拷贝Cb。

    Cb::Fun1  Ca::Fun2

    然后。由于Cc由Fun1和~Cc虚函数,产生覆盖。

    最后。覆盖后的Cc的虚表如下:

    Cc::~Cc  Cc:Fun1  Ca::Fun2

    Cc::Fun1  Ca::Fun2

    如果对虚表的结构了如执掌了。那么我们就可以通过数组下标的方式访问虚表,就可以突破编译器的限制! 当然不建议这么做!

    现在,我们来实战一把。

    题目如下:

    “不能使用virtual关键字 ,模拟虚函数来表现出多态性:

    写一基类Person 有sayHello,sayGoodbye函数

    有一子类student 它也有自己的sayHello, sayGoodbye函数

    请在这两个类里加入函数 vsayHello, vsayGoodbye函数

    来表现出对象的多态性(分别调用自己的对应的sayHello和sayGoodbye)“

    附带源码:

    // Person.h: interface for the CPerson class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #if !defined(AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_)
    #define AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    class CPerson;
    typedef void (CPerson::*CPERSON_PFUN)();
    
    class CPerson  
    {
    public:
      void vsayGoodbye();
      void vsayHello();
        void sayGoodbye();
        void sayHello();
        CPerson();
      CPerson(const int* pFun);
        ~CPerson();
    protected:
      static CPERSON_PFUN m_g_cPerpFun[2];
      CPERSON_PFUN m_cpFun[2];
    };
    
    #endif // !defined(AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_)
    // Person.cpp: implementation of the CPerson class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "Person.h"
    #include <iostream.h>
    #include <string.h>
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    
    CPERSON_PFUN CPerson::m_g_cPerpFun[2] = {CPerson::sayHello, CPerson::sayGoodbye};
    
    CPerson::CPerson()
    {
      // 模拟虚表赋值
      memcpy(m_cpFun, m_g_cPerpFun, sizeof(m_cpFun));
    }
    
    // 派生类构造前先构造父类虚表
    CPerson::CPerson(const int* pFun)
    {
      memcpy(m_cpFun, pFun, sizeof(m_cpFun));
    }
    
    CPerson::~CPerson()
    {
      // 模拟还原虚表
      memcpy(m_cpFun, m_g_cPerpFun, sizeof(m_cpFun));
    }
    
    void CPerson::sayHello()
    {
      cout << "CPerson::sayHello()" << endl;
    }
    
    void CPerson::sayGoodbye()
    {
      cout << "CPerson::sayGoodbye()" << endl;
    }
    
    void CPerson::vsayHello()
    {
      (this->*(m_cpFun[0]))();
    }
    
    void CPerson::vsayGoodbye()
    {
      (this->*(m_cpFun[1]))();
    }
    // Student.h: interface for the CStudent class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #if !defined(AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_)
    #define AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    #include "Person.h"
    
    class CStudent;
    typedef void (CStudent::*STUDENT_PFUN)();
    
    class CStudent : public CPerson  
    {
    public:
      void vsayGoodbye();
        void vsayHello();
      void sayGoodbye();
        void sayHello();
        CStudent();
      // VC6.0测试:
      // 发现编译器一个BUG,在析构函数前写上virtual关键字,则报错,会
      // 导致m_g_cStudentpFun成员数组在分配总空间上为16个字节。
    
      // Visual Stodio2005加和不加virtual关键字都测试正常.
        ~CStudent();
    private:
      static STUDENT_PFUN m_g_cStudentpFun[2];
    };
    
    #endif // !defined(AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_)
    // Student.cpp: implementation of the CStudent class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "Student.h"
    #include <iostream.h>
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    
    // 模拟编译器分配虚表信息
    
    STUDENT_PFUN CStudent::m_g_cStudentpFun[2] = {CStudent::sayHello, CStudent::sayGoodbye};
    
    CStudent::CStudent() : CPerson((int*)CStudent::m_g_cStudentpFun)
    {
      
    }
    
    CStudent::~CStudent()
    {
    }
    
    void CStudent::sayHello()
    {
      cout << "CStudent::sayHello()" << endl;
    }
    
    void CStudent::sayGoodbye()
    {
      cout << "CStudent::sayGoodbye()" << endl;
    }
    
    void CStudent::vsayHello()
    {
      (this->*(m_cpFun[0]))();
    }
    
    void CStudent::vsayGoodbye()
    {
      (this->*(m_cpFun[1]))();
    }
    // Work2.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "Person.h"
    #include "Student.h"
    
    int main(int argc, char* argv[])
    {
      CStudent theStu;
    
      CPerson* pObj = &theStu;
      // 多态性
      pObj->vsayHello();
      pObj->vsayGoodbye();
      return 0;
    }
  • 相关阅读:
    消息队列接口API(posix 接口和 system v接口)
    Ubuntu 安装 Eclipse C/C++开发环境
    Ubuntu下Eclipse搭建ARM开发环境
    Linux进程间通信——使用流套接字
    Linux进程间通信——使用数据报套接字
    Linux进程间通信——信号集函数
    Linux进程间通信——使用信号
    Linux进程间通信——使用匿名管道
    mappedBy的作用
    VS Code 配置 C/C++ 环境
  • 原文地址:https://www.cnblogs.com/ziolo/p/3061355.html
Copyright © 2011-2022 走看看