java有虚函数表_C++虚函数以及虚函数表

在了解虚函数之前先了解下对象模型:

对象模型: 在C++中,有两种数据成员(class data members):static 和nonstatic,以及三种类成员函数(class member functions):static、nonstatic和virtual:

cfe14defd25084268400880be2bdbe15.png

说明:采用的是非继承下的C++对象模型:

nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持,具体如下:

每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列,不过当子类有多个重载函数时例外。

每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端。

另外,虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。

81c072511a9b723e0b396376c3e90f08.png

c25b0ed935113746c722579cfaeebcb0.png

圆圈代表类中可以供每个对象共享的部分,矩形表示每个对象自己含有的部分。

简介

虚函数(virtual)是为了c++实现多态而采用的方法,并使用了动态绑定的技术-虚函数表。每一个包含虚函数的类都会有一个虚表,若基类含有虚函数,则派生类也要有自己的虚表。 通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。

例如: 类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。

class A {

public:

virtual void vfunc1();

virtual void vfunc2();

void func1();

void func2();

private:

int m_data1, m_data2;

};

则类A的虚表(虚表只包含虚函数)如图:

77c6e5ca5eef4c930631a3016cd432a4.png

虚表其实是一个指针数组,每个元素是指向虚函数的指针,普通函数为非虚函数,不需要通过虚表,虚表内的条目即虚函数指针的赋值发生在编译器的编辑阶段,在代码编译时,就已经构造出来了。

2.虚表指针

虚表属于类所有,不属于某个具体的对象。一个类只需要有一个虚表即可。同一个类的对象使用同一个虚表。为了指定对象的虚表,对象内部包含一个指向虚表的指针,来指向自己所使用的虚表。 为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表 当类的对象在创建是便有了这个指针,且指针的值会自动被设置为指向类的虚表。

c7479e114454011fba9c63007dbe1a88.png

说明:一个继承类的基类如果包含虚函数,那个这个继承类也有拥有自己的虚表,故这个继承类的对象也包含一个虚表指针,用来指向它的虚表。

4.动态绑定

class A {

public:

virtual void vfunc1();

virtual void vfunc2();

void func1();

void func2();

private:

int m_data1, m_data2;

};

class B : public A {

public:

virtual void vfunc1();

void func1();

private:

int m_data3;

};

class C: public B {

public:

virtual void vfunc2();

void func2();

private:

int m_data1, m_data4;

};

对象模型如图:

aadb4a429714b6b3c9b56a82c49e7821.png

说明:A是基类,B继承了A,C又继承了B。三个类中都有虚函数,故编译器为每个类都会创建一个虚表。每个类中的每个对象都有一个虚表指针,指向自己所属类的虚表。

类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。

类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。

类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。

法则:对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向器继承的最近一个类的虚函数

5.父类通过虚表指针访问子类的虚函数

int main()

{

B bObject;

A *p = & bObject;

}

说明:bObject是类B的一个对象,故bObject包含一个虚表指针。声明一个类A的指针指向对象bObject。

以上代码执行步骤:

根据虚表指针p->__vptr来访问对象bObject对应的虚表。虽然指针p是基类A*类型,但是*__vptr也是基类的一部分,所以可以通过p->__vptr可以访问到对象对应的虚表。

在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目。

根据虚表中找到的函数指针,调用函数。从图3可以看到,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。

97b67cbef9bc396d6e52492b504889e0.png  《-------》    指针p是基类A*类型,但是*__vptr也是基类的一部分的理解。

6.静态绑定和动态绑定,运行时多态,编译时多态

把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。 动态绑定的三个条件:

通过指针来调用函数

指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast,可以参考本文的参考资料)

调用的是虚函数

符合三个条件,编译器就会把该函数调用编译为动态绑定,调用时走虚表的机制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值