您的位置:首页 > 其它

漫谈继承技术(八)

2016-11-27 20:26 162 查看
       如果没有继承,类只是具有一些相关行为的数据结构,这只是对过程语言的一大改进,而继承则开辟了完全不同的新天地。通过继承,可以在已有类的基础上创建新类。这样,类就成为可重用和可扩展的组件。博主的《漫谈继承技术》系列博文将讲述各种利用继承功能的方法。学习继承的语法,和利用继承的一些复制技术。本篇博文先给大家介绍一下virtual在继承中的使用,希望对大家加深对继承技术的理解有一定的帮助。

virtual

       在C++编译类时,会创建一个包含类中所有方法的二进制对象。在非虚情况下,将控制交给正确方法的代码是硬编码,此时会根据编译时的类型调用方法。如果方法声明为virtual,会使用名为虚表(vtable)的特定内存区域调用正确的实现。每个具有一个或者多个虚方法的类都有一张虚表,这种类的每个对象都包含指向虚表的指针,这个虚表包含了指向虚方法实现的函数入口地址。通过这种方法,当使用某个对象调用方法时,指针也进入虚表,然后根据实际的对象类型执行正确版本的方法。

举个栗子,咱们理解一下重写技术。



       可能有的人会想:“我们为什么不把所有的函数都声明为virtual?那样就不需要virtual关键字,编译器直接隐藏这些细节,就像Java那样该多好啊!”。这样就会降低C++的性能,还会让C++程序猿受到束缚。要调用virtual方法,程序需要执行一些附加操作,即对指向要执行的适当代码的指针解除应用。在多数情况下,这样做会轻微地影响性能,但是C++的设计者认为,最好让程序猿决定是否有必要影响性能。如果方法永远不会被重写,就没有必要将其声明为virtual,从而影响性能。将析构函数声明为virtual永远都是一个好习惯,唯一允许不把析构函数声明为virtual的例外情况是,类标记为final。如果忘记了final的用法,那就回到《漫谈继承技术:一》再回顾一下吧!

       除非有特别原因,或者类标记为final,否则强烈建议将所有方法(包括析构函数,构造函数除外)声明为virtual。构造函数不需要也无法声明为virtual,因为在创建对象时,总会明确地指定类。

虚基类

      将共享基类设置为虚基类,这时从不同的路劲继承来的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射。这样就解决了同名成员的唯一标识问题。在多继承情况下,虚基类关键字的作用范围和继承方式关键字一样,只对紧跟其后的基类起作用。
       建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化。而且,只有最远派生类的构造函数会调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用都自动被忽略。
举个栗子:
#include<iostream>
usingnamespacestd;
 
//公共基类Base0
classBase0
{
public:
    Base0(intvar= 0):var0(var){}
   
intvar0;           //间接基类的数据成员
   
voidfun0()         //间接基类的成员函数
    {
        cout<<"Base0: "<< var0 << endl;
    }
};
 
//以Base0为虚基类,派生Base1
classBase1:virtualpublicBase0
{
public:
    Base1(intvar= 1):Base0(var),var1(var){}
   
voidfun()
    {
        cout<<"Base1: "<< var1 << endl;
    }
private:
   
intvar1;       //直接基类的数据成员
};
 
//以Base0为虚基类,派生Base2
classBase2:virtualpublicBase0
{
public:
    Base2(intvar= 2):Base0(var),var1(var){}
 
private:
   
intvar1;       //直接基类的数据成员
};
 
//最远派生类
classBase3:publicBase1,public
Base2
{
public:
    Base3(intvar= 3):Base0(var),Base1(var),Base2(var),
var(var){}
 
private:
   
intvar;            //最远派生类新增的数据成员
};
 
 
int main()
{
   
//实例化一个最远派生类的对象
   
Base3 b;       
   
//实例化一个直接基类的对象,此时Base2也是最远派生类。可以当做Base3不存在
   
Base2 c(6);    
 
    b.fun0();      //输出Base0: 3
    c.fun0();      //输出Base0: 6
    b.fun();       //输出Base1: 3
 
   
//通过直接基类调用fun0函数,实际是映射到间接基类(映射和副本只有一份)
    b.Base1::fun0();   //输出Base0: 3
    b.Base2::fun0();   //输出Base0: 3
 
   
return0;
}
 
程序运行结果:

 


虚基类的内存布局:



 

         如果想了解更多关于继承技术相关的知识,请关注博主《漫谈继承技术》系列博文,相信你能够在那里寻找到更多有助你快速成长和深入你对继承相关的知识和一些复制的技术理解和掌握。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息