理解虚基类、虚函数与纯虚函数的概念

来源:互联网 发布:java异步日志系统 编辑:程序博客网 时间:2024/06/08 14:48

引言

    
一直以来都没有写过一篇关于概念性的文章,因为我觉得这些概念性的东西书本上都有并且说的也很详细写来也无用,今天突发奇想想写一写,下面就和大家讨论一下虚基类、虚函数与纯虚函数,一看名字就让人很容易觉得混乱。不过不要紧待看完本文后你就会理解了。

正文

      
虚基类
      
在说明其作用前先看一段代码

class A
{
public:
    int iValue;
};

class B:public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //错误,不明确的访问
    cout<<d.A::iValue<<endl; //正确
    cout<<d.B::iValue<<endl; //正确
    cout<<d.C::iValue<<endl; //正确
}


从代码中可以看出类B C都继承了类AiValue成员,因此类B C都有一个成员变量iValue,而类D又继承了B C,这样类D就有一个重名的成员 iValue(一个是从类B中继承过来的,一个是从类C中继承过来的).在主函数中调用d.iValue因为类D有一个重名的成员iValue编译器不知道调用从谁继承过来的iValue所以就产生的二义性的问题.正确的做法应该是加上作用域限定符 d.B::iValue 表示调用从B类继承过来的iValue。不过D的实例中就有多个iValue的实例,就会占用内存空间。所以C++中就引用了虚基类的概念,来解决这个问题。

class A
{
public:
    int iValue;
};

class B:virtual public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:virtual public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //正确
}


在继承的类的前面加上virtual关键字表示被继承的类是一个虚基类,它的被继承成员在派生类中只保留一个实例。例如iValue这个成员,从类 D这个角度上来看,它是从类B与类C继承过来的,而类B C又是从类A继承过来的,但它们只保留一个副本。因此在主函数中调用d.iValue时就不会产生错误。 

      
虚函数
      
还是先看代码

class A
{
public:
    void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定义基类的指针
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}


大家以为这段代码的输出结果是什么?有的人可能会马上回答funPrint of class A funPrint of class B因为第一次输出是引用类A的实例啊,第二次输出是引用类B的实例啊。那么我告诉你这样想就错啦,答案是funPrint ofclass A funPrint of class A至于为什么输出这样的结果不在本文讨论的范围之内;你就记住,不管引用的实例是哪个类的当你调用的时候系统会调用左值那个对象所属类的方法。比如说上面的代码类A B都有一个funPrint函数,因为p是一个A类的指针,所以不管你将p指针指向类A或是类B,最终调用的函数都是类AfunPrint函数。这就是静态联篇,编译器在编译的时候就已经确定好了。可是如果我想实现跟据实例的不同来动态决定调用哪个函数呢?这就须要用到虚函数(也就是动态联篇)

class A
{
public:
    virtual void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    virtual void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定义基类的指针
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}


在基类的成员函数前加virtual关键字表示这个函数是一个虚函数,所谓虚函数就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数,要实现虚函数必须派生类的函数名与基类相同,参数名参数类型等也要与基类相同。但派生类中的virtual关键字可以省略,也表示这是一个虚函数。下面来解决一下代码,声明一个基类的指针(必须是基类,反之则不行)p,把p指向类A的实例a,调用funPrint函数,这时系统会判断p所指向的实例的类型,如果是A类的实例就调用A类的funPrint函数,如果是B类的实例就调用B类的funPrint函数。

    
纯虚函数 
    
与其叫纯虚函数还不如叫抽象类,它只是声明一个函数但不实现它,让派生类去实现它,其实这也很好理解。 

class Vehicle
{
public:
    virtual void PrintTyre()=0//纯虚函数是这样定义的
};

class Camion:public Vehicle
{
public:
    virtual void PrintTyre(){cout<<"Camion tyre four"<<endl;};
};

class Bike:public Vehicle
{
public:
    virtual void PrintTyre(){cout<<"Bike tyre two"<<endl;};
};

void main()
{
    Camion c;
    Bike b;
    b.PrintTyre();
    c.PrintTyre();
}


如上代码,定义了一个交通工具类(Vehicle),类中有一函数可打印出交通工具的轮胎个数,但交通工具很多轮胎个数自然也就不确定,所以就把它定义为纯虚函数,也就是光定义函数名不去实现它,类Camion继承了Vehicle并实现了里面的代码,打印出有4个轮胎。Bike类也是一样。有一点须要注意一下,纯虚函数不能实化化,但可以声明指针。


总结

    
虚基类 
    1,
一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。 
    2,
在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。 
    3,
虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。 
    4,
最派生类是指在继承结构中建立对象时所指定的类。 
    5,
派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。 
    6,
从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。 
    7,
在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。 

    
虚函数 
    1,
虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数可以在另一个类中被声明为友元函数。 
    2,
虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。 
    3,
一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 
    4,
若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时,对该成员函数调用可采用动态联编。 
    5,
定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。纯虚函数版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。

    
纯虚函数 
    1,
当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。 
    2,
纯虚函数的作用是为派生类提供一个一致的接口。 
    3,
纯虚函数不能实化化,但可以声明指针。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 拍拍贷不放款了怎么办 拍拍贷账号注销了怎么办 我在拍拍贷注销了怎么办 快贷逾期一年了怎么办 广州车牌买新车旧车怎么办 高尔夫旅行款被锁在车内怎么办 英雄联盟误删文件怎么办 拍拍贷换了号码怎么办 手机打开显示无法连接服务器怎么办 剑灵画面卡顿怎么办 cf被永久禁赛了怎么办 微信没有微游戏商店怎么办 游侠云盒下载慢怎么办2018 安卓手机玩网页游戏卡怎么办 safari点开什么都没有怎么办 康佳电视全网搜索打不开怎么办 脚被图钉扎了怎么办 电脑中毒了打不开软件怎么办 剑三程序不兼容怎么办 玩无主之地卡怎么办 平台老板跑路了怎么办 qq在苹果下载不了怎么办 下载速度快上传速度慢怎么办 苹果7开网页慢怎么办 为什么浏览器下载视频速度慢怎么办 机连WLAN网速慢怎么办 会声会影卸载后无法重新安装怎么办 电视空间不足无法卸载怎么办 堡垒之夜下载慢怎么办 手机网盘下载速度慢怎么办插件 笔记本电脑显示连接不可用怎么办 蓝魔手机充电慢怎么办 360f4手机充电慢怎么办 vivo卡了怎么办小窍门 白色衣服用84泡后变黄怎么办 用祛斑霜脱皮了怎么办 用祛斑霜脸一直蜕皮怎么办 吃热的就流鼻涕怎么办 键盘qaz失灵其他都没事怎么办 时时彩代理抓了怎么办 6p升级ios11卡顿怎么办