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;
}
时间: 2024-09-28 08:21:15

C++:虚基类的相关文章

C++中虚基类

摘自<C++程序设计> 如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员. C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员. 下面举例说明: 在如下的图中: Person类是Student和Teacher的基类,而Graduate类又继承自Student和Teacher类. 如果使用虚基类的话,Graduate将有两份age拷贝,两份gender拷贝,两份name

C++的虚基类知识点

当在多条继承路径上有一个公共的基类,在这些路径的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类. class x1:virtual public x{//... ...};class x2:virtual public x{//... ...};虚基类的初始化 虚基类(虚拟继承)的初始化与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同. 派生类的构造函数的调用次序有三个原则:(1)虚基类的构造函数在非虚基类之

第十二周 阅读项目 (4)虚基类多重继承数据理解

<span style="font-size:18px;">/* *Copyright (c)2014,烟台大学计算机与控制工程学院 *All rights reserved. *文件名称:d.cpp *作 者:张旺华 *完成日期:2015年6月1日 *版 本 号:v1.0 */ #include<iostream> using namespace std; class A { public: int n; }; class B:virtual public A

C++虚基类详解

1.虚基类的作用从上面的介绍可知:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如    c1.A::display( ).在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的.C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员.现在,将类A声明为

虚基类练习:动物(利用虚基类建立一个类的多重继承,包括动物(animal,属性有体长,体重和性别),陆生动物(ter_animal,属性增加了奔跑速度),水生动物(aqu_animal,属性增加了游泳速度)和两栖动物(amp_animal)。)

Description 长期的物种进化使两栖动物既能活跃在陆地上,又能游动于水中.利用虚基类建立一个类的多重继承,包括动物(animal,属性有体长,体重和性别),陆生动物(ter_animal,属性增加了奔跑速度),水生动物(aqu_animal,属性增加了游泳速度)和两栖动物(amp_animal).其中两栖动物保留了陆生动物和水生动物的属性. Input 两栖动物的体长,体重,性别,游泳速度,奔跑速度(running_speed) Output 初始化的两栖动物的体长,体重,性别,游泳速度

C++中 引入虚基类的作用

当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射.可以使用作用域分辨符来唯一标识并分别访问它们.也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中只拥有一个拷贝,同一个函数名也只有一个映射.也就是说虚基类解决了同名成员的唯一标识问题.

虚基类的用法

1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 private: 8 int a; 9 public: 10 A(int x):a(x){} 11 void show() const 12 { 13 cout<<"a: "<<a<<endl; 14 } 15 ~A(){} 16 }; 17 class B:virtual public A //定义虚基类的用

虚基类初始化问题

在包含有继承关系的类里,生成一个派生类对象,要调用构造函数进行初始化此对象,而构造函数的调用顺序是先调用最顶层基类的构造函数,次顶层....等:但在普通继承和虚继承里存在区别 普通继承:父类只能由其直接派生类初始化 1 class A 2 { 3 char a; 4 public: 5 A(char _a) :a(_a){ cout << a << endl; } 6 }; 7 8 class B:public A 9 { 10 char b; 11 public: 12 B(ch

一目了然c++虚基类!

1 #include <IOSTREAM.H> 2 //基类 3 class CBase 4 ...{ 5 protected: 6 int a; 7 public: 8 CBase(int na) 9 ...{ 10 a=na; 11 cout<<"CBase constructor! "; 12 } 13 14 ~CBase()...{cout<<"CBase deconstructor! ";} 15 }; 16 17 //