C++多态相关关问题及虚表剖析

关于C++多态的问题:(基于Visual Studio 2012编译器)

 

一、多态引入

1、对象的类型:

(1)   静态的类型:对象声明时的类型,在编译的时候确定

(2)   动态的类型:目前所指对象的类型,在程序运行时确定的

EG:

class Derived1:public Base

{};

 

class Derived2:public Base

{};

 

void FunTest()

{

   Derived1* pD1 = new Derived1;//对象pD1的静态类型是Derived1, 动态类型是Derived1*

   Base* pB = pD1;//对象pB的静态类型是Base*, 动态类型是Derived1*

   Derived2* pD2 = new Derived2;

   pB = pD2;//对象pB的静态类型是Base*,动态类型是Derived2

}

 

2、多态:

(1)   多态的概念:一词最初来源于希腊语,意思是具有多种形式或形态的情形,在C++中主要体现在想不通的对象发送同一个消息,不同的对象会产生不同的行为。

(2)   多态的类型:

A:一种为编译时的多态,称为静态多态或静态联编。编译时的多态性是指程序在编译以前就确定的多态性,可以通过重载、泛型编程来实现。

B:另一种是运行时的多态,也称为静态联编,运行时的多态是指在程序运行中才可以确定的多态性,是通过继承和虚函数实现的。

(3)   动态的多态:

动态绑定(早绑定):在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

class CWashRoom

{

public:

   void GoToManWashRoom ()

    {

   cout<< "Man--->Please Left" <<endl;

    }

   void GoToWomanWashRoom ()

    {

   cout<< "Woman--->Please Right" <<endl ;

    }

};

class CPerson

{

public:

   virtual void GoToWashRoom(CWashRoom & _washRoom ) = 0;//virtual修饰函数为虚函数,在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

};

 

class CMan:public CPerson

{

public:

   virtual void GoToWashRoom(CWashRoom & _washRoom )//对基类函数的重写,进行动态绑定,实现不同的功能。

    {

   _washRoom.GoToManWashRoom();

    }

};

class CWoman:public CPerson

{

public:

   virtual void GoToWashRoom(CWashRoom & _washRoom )

    {

   _washRoom.GoToWomanWashRoom ();

    }

};

                   注:必须是虚函数,通过基类类型的引用或者指针调用虚函数

                            voidFunTest()

{

CWashRoom washRoom;

for ( int iIdx = 1 ; iIdx <= 10 ; ++iIdx)

{

CPerson* pPerson;

int iPerson = rand ()%iIdx;

if (iPerson&0x01)

{

pPerson = new CMan ;

}

else

{

pPerson = new CWoman ;

}

pPerson->GoToWashRoom (washRoom); //通过基类类型的指针调用在派生类中//进行重写的虚函数,

delete pPerson;

pPerson = NULL ;

Sleep(1000 );

}

}

3、    继承体系中同名成员函数的关系和区别


4、  小结

1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)

2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。

3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。

4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆

6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,没有传递this指针,可能会出现未定义的行为。

7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)

8、虚表是所有类对象实例共用的

 

 

5、  虚表分析

(1)      单继承:

派生类中没有重写基类的虚函数

classBase

{

public:

    Base()

    {}

 

    virtualvoid Test1()

    {

        //data = 10;

        //cout<<this<<endl;

        //cout<<data<<endl;

        cout<<"BaseTest1()"<<endl;

    }

 

    virtualvoid Test2()

    {

        cout<<"BaseTest2()"<<endl;

    }

 

    virtualvoid Test3()

    {

        cout<<"BaseTest3()"<<endl;

    }

 

    int data;

};

 

classDerited:publicBase

{

public:

    virtualvoid Test4()//复制一份基类虚函数,如果在派生类中对基类虚函数进行重写,则用重写的虚函数代替

    {

        cout<<"DeritedTest4()"<<endl;

    }

 

    virtualvoid Test5()

    {

        cout<<"DeritedTest5()"<<endl;

    }

    virtualvoid Test6()

    {

        cout<<"Deritedtest6()"<<endl;

    }

};

void FunTest2(Base& b)

{

    b.Test2();

}

 

typedef void (*VFP)();

 

void printVpf()

{

    Base b;

    b.data1  = 1;

    cout<<"Base::virtable of B:"<<endl;

    VFP* vfp = (VFP *)*(int *)&b; //未传递this指针。

                                  //最好不要访问类的成员变量

    while(*vfp)

    {

        (*vfp)();

        ++vfp;

    }

    cout<<endl;

 

    Derited d;

    d.data1 = 1;

    d.data2 = 2;

    cout<<"Base::virtable of D:"<<endl;

    VFP* vfp1 = (VFP *)*(int *)&d;

 

    while(*vfp1)

    {

        (*vfp1)();

        ++vfp1;

    }

    cout<<endl;

}



在派生类中重写基类的虚函数

 

classBase

{

public:

    Base()

    {}

 

    virtualvoid Test1()

    {

        //data = 10;

        //cout<<this<<endl;

        //cout<<data<<endl;

        cout<<"BaseTest1()"<<endl;

    }

 

    virtualvoid Test2()

    {

        cout<<"BaseTest2()"<<endl;

    }

 

    virtualvoid Test3()

    {

        cout<<"BaseTest3()"<<endl;

    }

 

    int data;

};

 

classDerited:publicBase

{

public:

    virtualvoid Test1()//复制一份基类虚函数,如果在派生类中对基类虚函数进行重写,则用重写的虚函数代替

    {

        cout<<"DeritedTest1()"<<endl;

    }

 

    virtualvoid Test3()

    {

        cout<<"DeritedTest3()"<<endl;

    }

    virtualvoid Test4()

    {

        cout<<"test4()"<<endl;

    }

};

 

在重写了基类虚函数的派生类虚表中先复制了一份基类的虚表,然后将在派生类中重写的虚函数覆盖掉基类的虚函数,派生类自己的虚函数跟在复制下来的虚表后边。通过基类的指针或引用调用虚函数时调用的是被覆盖之后的虚表里的虚函数。

 

(2)      多继承:

class Base1

{

public:

 

  virtual void Test1()

  {

      cout<<"Base1Test1()"<<endl;

  }

 

  virtual void Test2()

  {

      cout<<"Base1Test2()"<<endl;

  }

 

  virtual void Test3()

  {

      cout<<"Base1Test3()"<<endl;

  }

 

  int data1;

};

 

class Base2

{

public:

  virtual void Test4()

  {

 

      cout<<"Base2Test4()"<<endl;

  }

 

  virtual void Test5()

  {

      cout<<"Base2 Test5()"<<endl;

  }

 

  virtual void Test6()

  {

      cout<<"Base2Test6()"<<endl;

  }

 

  int data2;

};

 

class Derited:public Base1,public Base2

{

public:

  Derited()

  {

      data3 = 3;

  }

  virtual void Test2()

  {

      cout<<"DeritedTest2()"<<endl;

  }

 

  virtual void Test4()

  {

      cout<<"DeritedTest4()"<<endl;

  }

  virtual void Test7()

  {

      cout<<"DeritedTest7()"<<endl;

  }

  int data3;

} ;

 

typedef void (* VFP)();

 

void printVpf()

{

  Derited d;

  d.data1 = 1;

  d.data2 = 2;

  d.data3 = 3;

  Base1 &b1 = d;

  VFP* vfp = (VFP *)*(int *)&b1;

  cout<<"&b1 ="<<&b1<<endl;                   

  while(*vfp)

  {

      (*vfp)();

      ++vfp;

  }

  cout<<endl;

 

  Base2 &b2 = d;

  cout<<"&b2 ="<<&b2<<endl;

  cout<<"&b2 ="<<(Base2 *)((int)&b1+sizeof(Base1))<<endl;

  VFP* vfp1 = (VFP *)*(int *)&b2;

 

  while(*vfp1)

  {

      (*vfp1)();

      ++vfp1;

  }

  cout<<endl;

  

 

 

 

 

 

在多继承中,基类的虚函数如果在子类中重写,则用基类的指针或引用调用虚函数,调用的是被覆盖的虚表里的虚函数。

 

 

 

(3)      菱形继承

class Base

{

public:

  virtual void Test1()

  {

      cout<<"Base::Test1()"<<endl;

  }

 

  int _data1;

};

class C1:public Base

{

public:

virtual void Test1()

  {

      cout<<"C1::Test1()"<<endl;

  }

virtual void Test2()

  {

      cout<<"C1::Test2()"<<endl;

  }

int _data2;

};

class C2:public Base

{

public:

virtual void Test1()

  {

      cout<<"C2::Test1()"<<endl;

  }

virtual void Test3()

  {

      cout<<"C2::Test3()"<<endl;

  }

int _data3;

};

class D:public C1,public C2

{

public:

virtual void Test1()

  {

      cout<<"D::Test1()"<<endl;

  }

virtual void Test2()

  {

      cout<<"D::Test2()"<<endl;

  }

virtual void Test3()

  {

      cout<<"D::Test3()"<<endl;

  }

virtual void Test4()

  {

      cout<<"D::Test4()"<<endl;

  }

int _data4;

};

typedef void(*VFP)();

void PrintVfp()

{

  D d;

  VFP* vfp1 = (VFP*)*(int *)&d;

  cout<<"D virtable:"<<endl;

  while(*vfp1)

  {

      (*vfp1)();

      ++vfp1;

  }

  cout<<endl;

 

  C2& c2 = d;

  VFP* vfp2 = (VFP*)*(int *)&c2;

  cout<<"C2 virtable:"<<endl;

  while(*vfp2)

  {

      (*vfp2)();

      ++vfp2;

  }

  cout<<endl;

 

  C1& c1 = d;

  VFP* vfp3 = (VFP*)*(int *)&c1;

  cout<<"C1 virtable:"<<endl;

  while(*vfp3)

  {

      (*vfp3)();

      ++vfp3;

  }

  cout<<endl;

Base& b = d;

}

int main()

{

  PrintVfp();

  D d;

  d.C1::_data1 = 0;

  d.C2::_data1 = 1;

  d._data2 = 2;

  d._data3 = 3;

  d._data4 = 4;

  system("pause");

  return 0;

}

 

在菱形继承中C1的大小为C1的成员加上Base的成员大小再加上C1的虚表大小

D的大小为C1的大小加上C2的大小加上D自己的成员变量大小。在派生类中存储了两份Base类的对象,故访问时存在二义性问题,由此引入了菱形虚拟继承。

 

 

(4)      菱形虚拟继承:

class Base

{

public:

    virtual void Test1()

    {

        cout<<"Base::Test1()"<<endl;

    }

 

    int _data1;

   

};

class C1:virtual public Base

{

public:

    virtual void Test1()

        {

            cout<<"C1::Test1()"<<endl;

        }

    virtual void Test2()

        {

            cout<<"C1::Test3()"<<endl;

        }

 

 

int _data2;

};

class C2:virtual public Base

{

public:

    virtual void Test1()

        {

            cout<<"C2::Test2()"<<endl;

        }

    virtual void Test3()

        {

            cout<<"C2::Test4()"<<endl;

        }

    int _data3;

};

class D:public C1,public C2

{

public:

    virtual void Test1()

        {

            cout<<"D::Test1()"<<endl;

        }

    virtual void Test4()

        {

            cout<<"D::Test4()"<<endl;

        }

        virtual void Test5()

        {

            cout<<"DTest5()"<<endl;

        }

    int _data4;

};

typedef void(*VFP)();

void PrintVfp()

{

    D d;

    VFP* vfp1 = (VFP*)*(int *)&d;

    cout<<"D virtable:"<<endl;

    while(*vfp1)

    {

        (*vfp1)();

        ++vfp1;

    }

    cout<<endl;

 

    C1 &c1 = d;

    VFP* vfp2 = (VFP*)*(int *)&c1;

    cout<<"C1 virtable:"<<endl;

    while(*vfp2)

    {

        (*vfp2)();

        ++vfp2;

    }

    cout<<endl;

 

    Base& b = d;

    VFP* vfp3 = (VFP*)*(int *)&b;

    cout<<"Base virtable:"<<endl;

    while(*vfp3)

    {

        (*vfp3)();

        ++vfp3;

    }

    cout<<endl;

}

int main()

{   cout<<sizeof(C1)<<endl;

    cout<<sizeof(D)<<endl;

    PrintVfp();

 

    system("pause");

    return 0;

}

 

C1的大小为20字节:基类Base成员data1大小+派生类C1成员data2大小+偏移指针+虚表指针

D的大小为36个字节大小:C1成员大小+C1虚表指针+C1偏移指针+C2成员大小+C2虚表指针+C2偏移指针+派生类D成员大小+基类Base虚表+Base成员

 

(5)      派生类带有构造函数+析构函数、构造函数、析构函数其中一个成员函数的虚表分析:

 

class Base

{

public:

    Base()

        :data1(1)

    {}

    ~Base()

    {}

    virtual void Test1()

    {

        cout<<"BaseTest1()"<<endl;

    }

 

    virtual void Test2()

    {

        cout<<"BaseTest2()"<<endl;

    }

 

    virtual void Test3()

    {

        cout<<"BaseTest3()"<<endl;

    }

 

    int data1;

};

class Derited:virtual public Base

{

public:

    Derited()

    {}

    ~Derited()

    {}

    virtual void Test1()

    {

        cout<<"DeritedTest1()"<<endl;

    }

    virtual void Test3()

    {

        cout<<"Deritedtest3()"<<endl;

    }

    virtual void Test4()

    {

        cout<<"DeritedTest4()"<<endl;

    }

    int data2;

};

typedef void (*VFP)()

void printVpf()

{

    Derited d;

    d.data1 = 1;

    d.data2 = 2;

    cout<<"vir table ofD:"<<endl;

    VFP* vfp1 = (VFP *)*(int *)&d;

 

    while(*vfp1)

    {

        (*vfp1)();

        ++vfp1;

    }

    cout<<endl;

    Base& b = d;

    cout<<"vir table ofB:"<<endl;

    VFP* vfp2 = (VFP *)*(int *)&b;

    while(*vfp2)

    {

        (*vfp2)();

        ++vfp2;

    }

    cout<<endl;

}

int main()

{

    printVpf();

    system("pause");

    return 0;

}

 

 

 

 

 

构造函数在这个地方的作用:

(1)      偏移量表地址的填写



(2)      调用基类构造函数,填写基类虚表地址:



(3)      填写派生类虚表地址:

 

(4)      重新填写基类虚表地址

 

 

(5)      插入派生类与基类分割0x000000


虚表:

 

此时派生类的大小为24字节:派生类虚表指针+派生类偏移指针+派生类成员大小+基类虚表指针+基类成员大小+分割的 :0x00000000

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459469.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++调用约定

<div class"markdown_views"><p>有一定C开发经验的人一定对”__cdecl、__stdcall、__fastcall”肯定不陌生吧&#xff01;但你真正理解了吗&#xff1f;是的&#xff0c;我曾在这采了无数个坑&#xff0c;栽了无数个跟头&#xff0c;终于忍无可忍要把它总…

C++动态绑定及返回类型协变

C多态之动态绑定&#xff1a; 1、概念&#xff1a;在程序执行期间(非编译期)判断所引用对象的实际类型&#xff0c;根据其实际类型调用相应的方法。 使用virtual关键字修饰类的成员函数时&#xff0c;指明该函数为虚函数&#xff0c;派生类需要重新实现&#xff0c;编译器将实…

使用ucontext组件实现的coroutine代码分析

coroutine一般翻译过来就是协程&#xff0c;类似于线程可以切换&#xff0c;而跟线程是由操作系统调度器来实现切换不一样&#xff0c;协程由用户程序自己调度进行切换。我以前也看过协程相关的内容&#xff0c;但没有自己去实现过。最近搞OpenStack&#xff0c;OpenStack各个模…

C++模板剖析:函数模板、类模板解析

C中关于模板&泛型编程问题&#xff1a; 问题引入&#xff1a;何编写一个通用加法函数&#xff1f; &#xff08;1&#xff09;使用函数重载&#xff0c;针对每个所需相同行为的不同类型重新实现它 int Add(const int &_iLeft, const int&_iRight) { return (_iL…

Android Studio 1.1的安装和遇到的坑

Google的Android Studio 出1.0稳定版本也有很久的时间了&#xff0c;一直喜欢Jetbrains公司的IDE&#xff0c;不同语言的IDE操作习惯都比较统一。 而Android Studio 是基于IntelliJ IDEA的社区版开发的 &#xff0c;怎么也要尝尝鲜才行。 今天安装了下&#xff0c;被几个小坑卡…

BestCoder Round #39 解题报告

现场只做出前三题w 不过不管怎样这既是第一次认真打BC 又是第一次体验用在线编译器调代码 订正最后一题花了今天一整个下午&#xff08;呜呜 收获还是比较大的^_^ Delete wld有n个数(a1,a2,...,an)&#xff0c;他希望进行k次删除一个数的操作&#xff0c;使得最后剩下的n−k个数…

linux :vim 实现命令行下输出进度条

1、 进度条原理&#xff1a; 进度条的的动态增长是利用人的视觉短暂停留效果的&#xff0c;不断从输出缓冲区刷新出相同的内容&#xff0c;在肉眼看来进度条在不断的增长。 在显示器上先输出[# ][%1] 刷新一次之后&#xff0c; …

***jquery选择器 之 获取父级元素、同级元素、子元素

一、获取父级元素1、 parent([expr]): 获取指定元素的所有父级元素 <div id"par_div"><a id"href_fir" href"#">href_fir</a><a id"href_sec" href"#">href_sec</a><a id"href_thr&q…

Sql Server字符串函数

字符串函数用于对字符和二进制字符进行各种操作 1.ASCII()函数 ASCII(character_expression)函数用于返回字符串表达式中最左侧的ASCII代码值。参数character_expression必须是一个char或varchar类型的字符串表达式。 eg: select ASCII(s),ASCII(sql),ASCII(1);执行结果如图:字…

linux 编辑器vim配置

1、 基本配置 对vim进行配置的目的&#xff1a; 进行vim配置&#xff0c;可以让我们在后续敲代码更加方便。按F5可以直接编译并执行C、C代码以及执行shell脚本&#xff1b;按“F8”可进行C、C代码的调试&#xff1b;“Ctrl A”为全选并复制快捷键&#xff0c;方便复…

centos 7上ambari安装试用

2019独角兽企业重金招聘Python工程师标准>>> 1、有三台centos7&#xff0c;名字分别是ws11.localdomain, ws12.localdomain, ws13.localdomain。配置epel源(安装epel-release)。 2、配置root用户ssh无密码访问。 3、安装ntp对时服务。 4、关闭防火墙。centos7下使…

进程管理—进程描述符(task_struct)

本文章转载自&#xff1a;http://blog.csdn.net/qq_26768741/article/details/54348586?locationNum4&fps1 前言 当把一个程序加载到内存当中&#xff0c;此时&#xff0c;这个时候就有了进程&#xff0c;关于进程&#xff0c;有一个相关的叫做进程控制块&#xff08;PCB&…

如何使用emacs编写c语言程序,并编译运行

vi和emacs被分别被称为编辑器之神和神之编辑器。vi的入门精通都很难&#xff0c;emacs入门容易&#xff0c;精通难&#xff1b;vi使用起来不停地切换模式&#xff0c;而emacs则不停地ctrl&#xff0c;meta等组合键。因此&#xff0c;高德纳大师说操作Emacs&#xff0c;就像弹奏…

操作系统中常见的进程调度算法

一、调度与调度算法 调度&#xff1a;操作系统管理了系统的有限资源&#xff0c;当有多个进程&#xff08;或多个进程发出的请求&#xff09;要使用这些资源时&#xff0c;因为资源的有限性&#xff0c;必须按照一定的原则选择进程&#xff08;请求&#xff09;来占用资源。这…

粘滞位 File文件内容

t权限&#xff08;粘滞位)&#xff1a; 是‘不可删除’权限&#xff0c;就是说即使某用户拥有这个文件的rwx权限&#xff0c;可以随意修改文件内容&#xff0c;但是就是不能删除&#xff0c;甚至不能修改文件名&#xff0c;只有root才行。t权限也可以直接用 chmod ot/at fil…

QQuickRenderControl

2019独角兽企业重金招聘Python工程师标准>>> http://doc.qt.io/qt-5/qquickrendercontrol.html http://translate.google.com.hk/translate?hlzh-CN&slauto&tlen&uhttp%3A%2F%2Fhabrahabr.ru%2Fpost%2F247477%2F http://www.kdab.com/overview-qt3d-2-…

线程的控制(创建、等待、终止)、分离线程

一、线程控制 1、线程&#xff1a;线程是资源调度的基本单位&#xff0c;线程是进程内部的一个执行流&#xff0c;在进程的地址空间内运行。在Linux 下没有真正意义上的线程&#xff0c;线程是用进程模拟的&#xff0c;又被称为轻量级进程。 2、由于同⼀一进程的多个线程共享同…

从netty-example分析Netty组件

分析netty从源码开始 准备工作&#xff1a; 1.下载源代码&#xff1a;https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程。 netty提供了一个netty-example工程&#xff0c; 分类如下&#xff1a; Fundamental Echo ‐ the very basic client and …

cep

cep posted on 2015-12-16 17:03 秦瑞It行程实录 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/ruiy/p/5051673.html

RHCS集群原理概述

一、 什么是RHCSRHCS是Red Hat Cluster Suite的缩写&#xff0c;也就是红帽集群套件&#xff0c;RHCS是一个能够提供高可用性、高可靠性、负载均衡、存储共享且经济廉价的集群工具集合&#xff0c;它将集群系统中三大集群架构融合一体&#xff0c;可以给web应用、数据库应用等提…