C++:虚基类

简介:

4.4.3 虚基类
1.没什么要引入虚基类
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多分同名成员。在访问这些同名的成员时,必须在派生类对象后增加直接基类名,使其惟一地标识一个成员,以免产生二义性。 

//例 4.15 虚基类的引例

复制代码
#include<iostream>
using namespace std;
class Base{              //声明类Base1和类Base2的共同的基类Base 
  public:
    Base()
    {
     a = 5;
     cout<<"Base Construction."<<endl;
     cout<<"Base a = "<<a<<endl;
    }
  protected:
    int a;    
};
class Base1:public Base{       //声明类Base1是Base的派生类 
  public:
    int b1;
    Base1()
    {
      a = a+10;
      cout<<"Base1 Construction."<<endl;
      cout<<"Base1 a = "<<a<<endl;      //这时类Base1的a,即Base1::a 
    }
}; 
class Base2:public Base{       //声明类Base2是Base的派生类 
  public:
    int b2;
    Base2()
    {
      a = a+20;
      cout<<"Base2 Construction."<<endl;
      cout<<"Base2 a = "<<a<<endl;      //这时类Base2的a,即Base2::a 
    }
};
class Derived:public Base1,public Base2{    //Derived是Base1和Base2的共同派生类,是Base的间接派生类 
  public:
    int d;
    Derived()
    {
      cout<<"Derived Construction."<<endl;
      cout<<"Base1::a = "<<Base1::a<<endl;  //在a前面加上"Base1::" 
      cout<<"Base2::a = "<<Base2::a<<endl;  //在a前面加上"Base2::"   
    }     
};
int main()
{
 Derived obj;
 return 0;
}
/*
运行结果如下:
 Base Construction.                    
 Base a = 5
 Base1 Construction.
 Base1 a = 15
 Base Construction.
 Base a = 5 
 Base2 Construction.
 Base2 a = 25
 Derived Construction.
 Base1::a =15
 Base2::a =25
 
 发现:Base1和Base2派生出的类Derived继承了基类Base两次,也就是说,基类Base的成员a保留两份。
 
  图4.3 例4.15的类层次图               图4.4 派生类Derived的成员情况 
    Base           Base                           Derived 
      |Base1    Base2|                          int Base1::a; 
          |Derived|                             int Base1::b1;
                                                int Base2::a;
                                                int Base2::b2;
                                                int d;
                                                Derived();
*/ 
复制代码

2.虚基类的概念
不难理解,如果在上例类Base只存在一个复制(即只有一个数据成员),那么对a的引用就不会产生二义性。在C++中,如果想使这个公共的基类只产生一个复制,则可以将这个基类说明为虚基类。这就要求从类Base派生新类时,使用关键virtual将类Base说明为虚基类。


虚基类在派生类中的声明格式如下:
class 派生类名:virtual 继承方式 基类名{
......
};
经过这样声明后,当基类通过多条派生路径被一个派生类继承时,则该派生类只继承该基类一次,也就是说,基类成员只保留了一次。 

//例4.16 虚基类的使用

 

复制代码
#include<iostream>
using namespace std;
class Base{              //声明类Base1和类Base2的共同的基类Base 
  public:
    Base()
    {
     a = 5;
     cout<<"Base Construction."<<endl;
     cout<<"Base a = "<<a<<endl;
    }
  protected:
    int a;    
};
class Base1:virtual public Base{       //声明类Base是Base1的虚基类 
  public:
    int b1;
    Base1()
    {
      a = a+10;
      cout<<"Base1 Construction."<<endl;
      cout<<"Base1 a = "<<a<<endl;       
    }
}; 
class Base2:virtual public Base{       //声明类Base是Base2的虚基类 
  public:
    int b2;
    Base2()
    {
      a = a+20;
      cout<<"Base2 Construction."<<endl;
      cout<<"Base2 a = "<<a<<endl;       
    }
};
class Derived:public Base1,public Base2{    //Derived是Base1和Base2的共同派生类,是Base的间接派生类 
  public:
    int d;
    Derived()
    {
      cout<<"Derived Construction."<<endl;
      cout<<"Derived a = "<<a<<endl;      
    }     
};
int main()
{
 Derived obj;
 return 0;
}
/*
运行结果如下:
 Base Construction.                    
 Base a = 5
 Base1 Construction.
 Base1 a = 15
 Base2 Construction.
 Base2 a = 35
 Derived Construction.
 Derived a =35
 
 发现:Base1和Base2派生出的类Derived只继承了基类Base一次,也就是说,基类Base的成员a只保留一份。
 
  
  图4.3 例4.15的类层次图               图4.4 派生类Derived的成员情况 
           |Base|                                  Derived 
(virtual)|Base1 Base2|(virtual)                    int a; 
          |Derived|                                int Base1::b1;
                                                   int Base2::b2;
                                                   int d;
                                                   Derived(); 
 */ 
复制代码

 

3.虚基类的初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。
在使用虚基类机制时应该注意如下几点:

(1)如果在虚基类中定义有带有形参的构造函数,并且没有定义默认形式的构造函数,则整个继承机构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出列出对虚基类构造函数的调用,以初始化在基类中定义的数据成员。

(2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数来进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。

(3)若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。

(4)对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(5)对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(6)若虚基类有非虚基类派生而来,则仍然先调用基类的构造函数,再调用派生类的构造函数。

例如(3)的举例:
    class X:public Y,virtual public Z{
              ......
    };
    X obj;
定义类X的对象obj后,将产生如下的调用次序。
    Z();
    Y();
    X();

//例4.17 虚基类的派生类构造函数的执行顺序

复制代码
#include<iostream>
using namespace std;
class Base{                                 //声明虚基类Base 
  public:
    Base(int sa)
    {
     a = sa;
     cout<<"Constructor Base"<<endl;
     cout<<"a="<<a<<endl;
    }
  private:
    int a;
};
class Base1:virtual public Base{           //声明类Base是类Base1的虚基类 
  public:                                  //在此必须缀上对类Base的构造函数的调用 
    Base1(int sa,int sb):Base(sa)
    {
     b = sb;
     cout<<"Constructor Base1"<<endl;
     cout<<"b="<<b<<endl;
    }
  private:
    int b;
};
class Base2:virtual public Base{           //声明类Base是类Base2的虚基类 
  public:                                  //在此必须缀上对类Base的构造函数的调用 
    Base2(int sa,int sc):Base(sa)
    {
     c = sc;
     cout<<"Constructor Base2"<<endl;
     cout<<"c="<<c<<endl;
    }
  private:
    int c;
}; 
class Derived:public Base1,public Base2{  //Derived是类Base1和类Base2的共同派生类,是Base的间接派生类 
  public:
    Derived(int sa,int sb,int sc,int sd):Base(sa),Base1(sa,sb),Base2(sa,sc)//必须缀上对类Base的
    {                                                                       //构造函数的调用
      d = sd;
      cout<<"Constructor Derived"<<endl;
      cout<<"d="<<d<<endl;
    } 
  private:                                                                    
    int d;
}; 
int main()
{
 Derived obj(2,4,6,8);
 return 0;
}
复制代码

发现:
在上述程序中,Base是一个虚基类,它只有一个带参数的构造函数,因此要求在派生类Base1、 Base2和Derived的构造函数的初始化表中,都必须带有对类Base的构造函数的调用。

注意:
如果Base不是虚基类,在派生类Derived的构造函数的初始化表中调用类Base的构造函数是错误的,但是当Base是虚基类且只有带参数的构造函数时,就必须在类derived的构造函数的初始化表中调用类Base的构造函数。因此,在类Derived构造函数的初始化表中,不仅含有对类Base1和Base2的构造函数的调用,还有对虚基类Base的构造函数的调用。

程序运行结果是:
Constructor Base
a=2
Constructor Base1
b=4
Constructor Base2
c=6
Constructor Derived
d=8
不难看出,上述程序中虚基类Base的构造函数只执行了一次。显然,当Derived的构造函数调用了虚基类Base的构造函数之后,类Base1和类Base2构造函数的调用被忽略了。这也就是初始化虚基类和初始化非虚基类不同的地方。

复制代码
说明:
1)关键字virtual与派生类方式关键字(publi、private)的先后顺序无关紧要,它只是说明
是"虚拟派生"。例如以下两个虚拟派生的声明是等价的。
    class Derived:virtual public Base{
       ......
    };
    class Derived:public virtual Base{
       ......
    };
    
2)一个基类在作为某些派生类虚基类的同时,又作为某些派生类非虚基类,这种情况是允许存在的,
例如:
    class B{
     ......
    };
    class X:virtual public B{
     ......
    };
    class Y:virtual public B{
     ......
    };
    class Z:public B{
     ......
    };
    class AA:public X,public Y,public Z{
     ......
    };
复制代码

4.虚基类的简单应用举例

例4.18 类Data_rec是虚基类,它包含了所有派生类共有的数据成员,职工类Employee和学生类Student为虚基类Data_rec的派生类,在职大学生类E_Student是职工类Employee和学生类Student的共同派生类。

复制代码
#include<iostream>
#include<string>
using namespace std;
class Data_rec{                                        //声明虚基类Data_rec 
   public:
      Data_rec(string name1,char sex1,int age1)        //虚基类的构造函数
      {
       name = name1;
       sex = sex1;
       age = age1;
      } 
   protected:
      string name;
      char sex;
      int age;
};
class Student:virtual public Data_rec{     //声明虚基类Data_rec的派生类Student
   public:
      //声明虚基类Data_rec的派生类Student的构造函数,并缀上虚基类构造函数的调用 
      Student(string name1,char sex1,int age1,string major1,double score1):Data_rec(name1,sex1,age1)
      {                   
       major = major1;
       score = score1;
      }
   protected:
      string major;
      double score; 
};
class Employee:public virtual Data_rec{     //声明虚基类Data_rec的派生类Employee
   public:
      //声明虚基类Data_rec的派生类Employee的构造函数,并缀上虚基类构造函数的调用 
      Employee(string name1,char sex1,int age1,string dept1,int salary1):Data_rec(name1,sex1,age1)
      {                   
       dept = dept1;
       salary = salary1;
      }
   protected:
      string dept;
      double salary; 
};
class E_Student:public Student,public Employee{//在职大学生类E_Student是职工类Employee和学生类Student的共同派生类
 //在职大学生类E_Student的构造函数,并缀上基类Data_rec和职工类Employee、学生类Student的构造函数的调用 
 public:          
  E_Student(string name1,char sex1,int age1,string major1,int score1,string dept1,double salary1):
  Data_rec(name1,sex1,age1),Student(name1,sex1,age1,major1,score1),Employee(name1,sex1,age1,dept1,salary1)
  {}
  void print();
};
void E_Student::print()
{
     cout<<"name:"<<name<<endl;
     cout<<"sex:"<<sex<<endl;
     cout<<"age:"<<age<<endl;
     cout<<"major:"<<major<<endl;
     cout<<"score:"<<score<<endl;
     cout<<"dept:"<<dept<<endl;
     cout<<"salary:"<<salary<<endl;
}
int main()
{
 E_Student obj("张大明",'',35,"计算机",95,"教务处",3500);
 obj.print();
 return 0;
}
复制代码

 

本文转自当天真遇到现实博客园博客,原文链接:http://www.cnblogs.com/XYQ-208910/p/4912437.html ,如需转载请自行联系原作者
相关文章
|
5G
IEEE 802.11 系列无线标准主要区别
【8月更文挑战第24天】
1256 0
|
Linux Docker 容器
Linux overlay文件系统解析
一个 overlay 文件系统包含两个文件系统,一个 upper 文件系统和一个 lower 文件系统,是一种新型的联合文件系统。overlay是“覆盖…上面”的意思,overlay文件系统则表示一个文件系统覆盖在另一个文件系统上面。
5024 0
|
10月前
|
人工智能 小程序 前端开发
一个小程序轻量AR体感游戏,开发实现解决方案
针对青少年运动兴趣不足问题,AR体感游戏凭借沉浸式互动体验脱颖而出。结合小程序“AI运动识别”插件与WebGL渲染技术,可实现无需外设的轻量化AR健身游戏,如跳糕、切水果等,兼具趣味性与锻炼效果,适用于儿童健身及职工团建,即开即玩,低门槛高参与。
|
SQL 关系型数据库 MySQL
MySQL因为hung住,自动重新启动,InnoDB: Semaphore wait has lasted > 600 seconds
从错误日志中检查,有两个信号量等待时间超长,MySQL自动crash了
917 0
|
存储 芯片
第六章 半导体存储器【微机原理】2
第六章 半导体存储器【微机原理】2
2118 0
|
存储 JavaScript 安全
跨站脚本攻击(XSS)和跨站请求伪造(CSRF)是什么?区别是什么?底层原理是什么?
跨站脚本攻击(XSS)和跨站请求伪造(CSRF)是什么?区别是什么?底层原理是什么?
2102 0
|
关系型数据库 MySQL Linux
linux添加开机自启动脚本示例详解
linux下(以RedHat为范本)添加开机自启动脚本有两种方法,先来简单的;一、在/etc/rc.local中添加如果不想将脚本粘来粘去,或创建链接什么的,则:step1. 先修改好脚本,使其所有模块都能在任意目录启动时正常执行;step2.
3795 0
|
存储 弹性计算 数据管理
阿里云对象存储OSS怎么收费?存储包流量包还是按量?
阿里云对象存储OSS收费有两种计费模式,即包年包月和按量付费,包年包月是指购买存储包、流量包来抵扣OSS产生的存储费核流量费,OSS标准(LRS)存储包100GB优惠价33元、500GB存储包半年162元、OSS存储包40GB一年9元,OSS流量包100G 49元/月
9588 2
阿里云对象存储OSS怎么收费?存储包流量包还是按量?

热门文章

最新文章