C++知识点总结(九)

  • 概述:
    • 为了实现C++的多态,C++使用了一种动态绑定的技术,这个技术的核心是虚函数表。下面介绍虚函数表是如何实现动态绑定的。
  • 类的虚表
    • 每个包含了虚函数的类都包含一个虚表;
    • 虚表中存的是函数地址(也就是函数入口/函数名);
    • 当一个类(A)继承了另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表
    • 以下的代码中,类A包含了虚函数vfun1,vfun2,由于类A包含虚函数,故类A拥有一个虚表指针。

                        ​​​​​​​        ​​​​​​​         

 

  • 虚表:
    • 虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针,需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数函数的函数指针
    • 虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。
  • 虚表指针:
    • 虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表
    • 为了指定对象的虚表,对象内部包含一个虚表的指针来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*_vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表
  • 动态绑定:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

  • 由于这三个类中都有虚函数,故编译器为每个类都创建了一个虚表,即类A的虚表(A vtbl),类B的虚表(B vtbl),类C的虚表(C vtbl)。类A,类B,类C的对象都拥有一个虚表指针,*_vptr,用来指向自己所属类的虚表;
  • 类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();
  • 非虚函数的调用不用经过虚表,故不需要虚表中的指针指向这些函数;

//假设我们定义一个类B的对象,由于bObject是类B的一个对象,故BObject包含一个虚表指针,指向类B的虚表;

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

//现在,我们声明一个类A的指针p来指向对象BObject,虽然p是基类的指针只能指向基类的部分,但是虚表指针亦属于基类部分,所以p可以访问到对象BObject的虚表指针。BObject的虚表指针指向类B的虚表,所以p可以访问到 B vtbl;

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

//程序执行到这里时,会去p指向的地址空间取出虚函数表指针,另外,这个时候p指向的对象是哪个子类已经确定了,只需要到这个地址取它的虚指针就没错了,如果p指向的Class A的对象地址,则取出的是Class A的虚指针。(虽然是父类的指针,但是它指向的还是子类的空间).。然后,就会顺利成章地调用到正确的虚函数了。

//当程序在执行p->vfunc1()时,会发现p是一个指针,且调用的函数是虚函数,接下来便会进行以下的步骤:

  • 首先,根据虚表指针p->_vptr来访问对象BObject对应的虚表,虽然指针p是基类A*类型,但是*_vptr也是基类的一部分,所以可以通过p->_vptr可以访问到对象对应的虚表
  • 然后在虚表中查找所调用的函数对应的条目,由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对应p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目;
  • 最后根据虚表中找到的函数指针,调用函数,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。
  • 可以把以上三个调用函数的步骤用以下表达式来表示:

    (*(p->__vptr)[n])

  • 我们可以看到,通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。
  • 我们把经过虚表调用虚函数的过程称为动态绑定,其表现出的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定
  • 执行函数动态绑定需要具备三个条件:
    • 通过指针来调用函数;
    • 指针向上转型(子类转成基类)
    • 调用的是虚函数

如果一个函数调用符合以上三个条件,编译器就会把该函数调用编译成动态绑定,其函数的调用过程走的是上述通过虚表的机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值