VC++ 之 虚基类详解

  在上一节中,有两个身份证号显然是不合理的。为此,可以把class Person这个共同基类设置为虚基类,这样,从不同路径继承来的同名数据成员在内存中就只有一个拷贝,同名函数也只有一种映射。

虚基类定义方式

虚基类(virtual base class)定义方式如下:
    class 派生类名:virtual 访问限定符 基类类名{...};
或:
    class 派生类名:访问限定符 virtual 基类类名{...};

其中:virtual 关键字只对紧随其后的基类名起作用。

例如:
    //学生类定义:
    class Student::virtual public Person{...};
    //教职工类定义:
    class Employee::virtual public Person{...};

采用虚基类的多重继承的特点

采用虚基类后,在职研究生类对象的储存如下图所示。



与8.3节中的图8.4(b)不同的是:在Person的位置上放的是指针,两个指针都指向Person成员存储的内存。这种继承称为虚拟继承(virtual inheritance)。

  采用虚基类的多重继承的构造与析构的次序

在派生类对象的创建中,构造次序如下:

  1. 虚基类的构造函数被调用,并按它们声明的顺序构造;
  2. 非虚基类的构造函数按它们声明的顺序调用;
  3. 成员对象的构造函数;
  4. 派生类自己的构造函数被调用。


析构的次序与构造的次序相反。

  应用举例

【例8.3】在采用虚基类的多重继承中,构造与析构的次序。

//【例8.3】在采用虚基类的多重继承中,构造与析构的次序。
#include<iostream>
using namespace std;

class Object{
public:
    Object(){cout<<"constructor Object\n";}
    ~Object(){cout<<"deconstructor Object\n";}
};
class Bclass1{
public:
    Bclass1(){cout<<"constructor Bclass1\n";}
    ~Bclass1(){cout<<"deconstructor Bclass1\n";}
};
class Bclass2{
public:
    Bclass2(){cout<<"constructor Bclass2\n";}
    ~Bclass2(){cout<<"deconstructor Bclass2\n";}
};
class Bclass3{
public:
    Bclass3(){cout<<"constructor Bclass3\n";}
    ~Bclass3(){cout<<"deconstructor Bclass3\n";}
};
class Dclass:public Bclass1,virtual Bclass3,virtual Bclass2{
    Object object;
public:
    Dclass():object(),Bclass2(),Bclass3(),Bclass1(){cout<<"派生类建立!\n";}
    ~Dclass(){cout<<"派生类析构!\n";}
};

int main(){
    Dclass dd;
    cout<<"主程序运行!\n";
    return 0;
}

运行结果
Constructor Bclass3 //第一个虚拟基类,与派生类析构函数排列无关
Constructor Bclass2 //第二个虚拟基类
Constructor Bclass1 //非虚拟基类
Constructor Object //对象成员
派生类建立!
主程序运行!
派生类析构!
deconstructor Object //析构次序相反
deconstructor Bclass1
deconstructor Bclass2
deconstructor Bclass3

示例 虚基类在多层多重继承中的应用——在职研究生类定义
//【例8.4】虚基类在多层多重继承中的应用--在职研究生类定义。
#include<iostream>
#include<string>
using namespace std;
enum Tsex{mid,man,woman};
//为简化,本例定义学生类时课程省略,并全部用string字符串
class Person{
    string IdPerson;                //身份证号
    string Name;                    //姓名
    Tsex Sex;                        //性别
    int Birthday;                    //生日,格式1986年8月18日写作19860818
    string HomeAddress;            //家庭地址
public:
    Person(string, string,Tsex,int, string);
    Person();
    ~Person();
    void PrintPersonInfo();
    //其他接口函数
};
Person::Person(string id, string name,Tsex sex,int birthday, string homeadd){
    cout<<"构造Person"<<endl;
    IdPerson=id;
    Name=name;
    Sex=sex;
    Birthday=birthday;
    HomeAddress=homeadd;
}
Person::Person(){
    cout<<"构造Person"<<endl;
    IdPerson=\0;Name=\0;Sex=mid;
    Birthday=0;HomeAddress=\0;
}
Person::~Person(){
    cout<<"析构Person"<<endl;
} // IdPerson, Name, HomeAddress析构时自动调用它们自己的析构函数来释放内存空间
void Person::PrintPersonInfo(){
    int i;
    cout<<"身份证号:"<<IdPerson<<\n<<"姓名:"<<Name<<\n<<"性别:";
    if(Sex==man)cout<<""<<\n;
    else if(Sex==woman)cout<<""<<\n;
         else cout<<" "<<\n;
    cout<<"出生年月日:";
    i=Birthday;
    cout<<i/10000<<"";
    i=i%10000;
    cout<<i/100<<""<<i%100<<""<<\n<<"家庭住址:"<<HomeAddress<<\n;
}
class Student:public virtual Person{           //以虚基类定义公有派生的学生类
    string NoStudent;                   //学号
    //30门课程与成绩略
public:
    Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud);
    //注意派生类构造函数声明方式
    Student();
    ~Student(){cout<<"析构Student"<<endl;}
    void PrintStudentInfo();
};
Student::Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud)
:Person(id,name,sex,birthday,homeadd){      //注意Person参数表不用类型
    cout<<"构造Student"<<endl;
    NoStudent=nostud;
}
Student::Student(){                        //基类缺省的无参数构造函数不必显式给出
    cout<<"构造Student"<<endl;
}
void Student::PrintStudentInfo(){
    cout<<"学号:"<<NoStudent<<\n;
    PrintPersonInfo();
}
class GStudent:public Student{                   //以虚基类定义公有派生的研究生类
    string NoGStudent;                      //研究生号
    //其他略
public:
    GStudent(string id, string name,Tsex sex,int birthday, string homeadd, string nostud,
            string nogstudent);                        //注意派生类构造函数声明方式
    GStudent();
    ~GStudent(){cout<<"析构GStudent"<<endl;};
    void PrintGStudentInfo();
};
GStudent::GStudent(string id, string name,Tsex sex,    int birthday, string homeadd,
 string nostud, string nogstud)
:Student(id,name,sex,birthday,homeadd,nostud),Person(id,name,sex,birthday,homeadd){
    //因Person是虚基类,尽管不是直接基类,如定义GStudent对象,Person必须出现。
    //不定义对象可不出现,为通用应出现。如不是虚基类,出现是错误的
    cout<<"构造GStudent"<<endl;
    NoGStudent=nogstud;
}
GStudent::GStudent(){                      //基类缺省的无参数构造函数不必显式给出
    cout<<"构造GStudent"<<endl;
}
void GStudent::PrintGStudentInfo(){
    cout<<"研究生号:"<<NoGStudent<<\n;
    PrintStudentInfo();
}
class Employee:public virtual Person{          //以虚基类定义公有派生的教职工类
    string NoEmployee;                  //教职工号
    //其他略
public:
    Employee(string id, string name,Tsex sex,int birthday, string homeadd, string noempl);
    //注意派生类构造函数声明方式
    Employee();
    ~Employee(){cout<<"析构Employee"<<endl;}
    void PrintEmployeeInfo();
    void PrintEmployeeInfo1();   //多重继承时避免重复打印虚基类Person的信息
};
Employee::Employee(string id, string name,Tsex sex,int birthday, string homeadd, string noempl)
:Person(id,name,sex,birthday,homeadd){    //注意Person参数表可不用类型
    cout<<"构造Employee"<<endl;
    NoEmployee=noempl;
}
Employee::Employee(){                    //基类缺省的无参数构造函数不必显式给出
    cout<<"构造Employee"<<endl;
}
void Employee::PrintEmployeeInfo(){
    cout<<"教职工号:"<<NoEmployee<<\n;
    PrintPersonInfo();
}
void Employee::PrintEmployeeInfo1(){cout<<"教职工号:"<<NoEmployee<<\n;}
class EGStudent:public Employee,public GStudent{ //以虚基类定义公有派生的在职研究生类
    string NoEGStudent;                          //在职学习号
    //其他略
public:
    EGStudent(string id, string name,Tsex sex,int birthday, string homeadd, string nostud,
        string nogstud, string noempl, string noegstud);
    //注意派生类构造函数声明方式
    EGStudent();
    ~EGStudent(){cout<<"析构EGStudent"<<endl;};
    void PrintEGStudentInfo();
};
EGStudent::EGStudent(string id, string name,Tsex sex,int birthday, string homeadd,
    string nostud, string nogstud, string noempl, string noegstud)
    :GStudent(id,name,sex,birthday,homeadd,nostud,nogstud),
    Employee(id,name,sex,birthday,homeadd,noempl),
    Person(id,name,sex,birthday,homeadd){ //注意要定义EGStudent对象,Person必须出现
    cout<<"构造EGStudent"<<endl;
    NoEGStudent=noegstud;
}
EGStudent::EGStudent(){                 //基类缺省的无参数构造函数不必显式给出
    cout<<"构造EGStudent"<<endl;
}
void EGStudent::PrintEGStudentInfo(){
    cout<<"在职学习号:"<<NoEGStudent<<\n;
    PrintEmployeeInfo1();   //多重继承时避免重复打印虚基类Person的信息
    PrintGStudentInfo();    // 虚基类Person的信息仅在GStudent中打印
}
int main(void){
    EGStudent egstu1("320102811226161","朱海鹏",man,19811226,"南京市黄浦路1号",
        "06000123",    "034189","06283","030217");
    egstu1.PrintEGStudentInfo();
    GStudent gstu1("320102820818161","沈俊",man,19820818,"南京四牌楼2号",
        "08000312","058362");
    gstu1.PrintGStudentInfo();
    return 0;
}

大学在册人员继承关系如下图所示:


图 大学在册人员继承关系


采用虚基类的在职研究生类的多重继承结构如下图所示:


运行时可以看到,尽管Employee和Student的构造函数都包含Person的构造函数,但并未真正调用。唯一的一次调用是在EGStudent构造函数中。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。