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 Bclass1deconstructor Bclass2deconstructor 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构造函数中。

时间: 2024-08-01 04:23:56

VC++ 之 虚基类详解的相关文章

C++虚基类详解

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

C++学习20 虚基类详解

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另一份来自 A-->C-->D 这一条路. 在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用

C++虚基类详解(转)

我们知道,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如:    c1.A::display( ) 在一个类中保留间接共同基类的多份同名成员,虽然有时是有必要的,可以在不同的数据成员中分别存放不同的数据,也可以通过构造函数分别对它们进行初始化.但在大多数情况下,这种现象是人们不希望出现的.因为保留多份数据成员的拷贝

详解C++中基类与派生类的转换以及虚基类

很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来了,在派生类外可以调用基类的公用成员函数访问基类的私有成员.因此,公用派生类具有基类的全部功能,所有基类能够实现的功能, 公用派生类都能实现.而非公用派生类(私有或保护派生类)不能实现基类的全部功能(例如在派生类外不能调用基类的公用成员函数访问基类的私有成员).因此,只有公用派生

C++:虚函数的详解

5.4.2 虚函数详解 1.虚函数的定义 虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数. 虚函数的定义是在基类中进行的,它是在基类中需要定义为虚函数的成员函数的声明中冠以关键字virtual.定义虚函数的格式如下: virtual 函数类型 函数名(形参表) {     函数体 } 在基类中的某个成员函数声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新

C++多态篇3——虚函数表详解之多继承、虚函数表的打印

在上上一篇C++多态篇1一静态联编,动态联编.虚函数与虚函数表vtable中,我最后简单了剖析了一下虚函数表以及vptr. 而在上一篇文章C++多态篇2--虚函数表详解之从内存布局看函数重载,函数覆盖,函数隐藏中我详细介绍了虚函数的函数重载,函数覆盖以及函数隐藏的问题,其实在那一篇文章中,对单继承的虚函数已经做了十分详细的解答了,如果对前面有兴趣的人可以先看一下那篇文章. 在这一篇中,我会具体的分析一下在不同继承中(单继承,多继承)关于虚函数表在内存中的布局以及如何打印虚函数表.但是有关在虚继承

虚方法virtual详解

虚方法virtual详解 从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数,其中那个申明时定义的类叫申明类,那个执行时实例化的类叫实例类. 如:飞禽 bird = new 麻雀();那么飞禽就是申明类,麻雀是实例类. 具体的检查的流程如下 1.当调用一个对象的函数时,系统会直接去检

JavaScript学习总结(十一)——Object类详解

一.Object类介绍 Object类是所有JavaScript类的基类(父类),提供了一种创建自定义对象的简单方式,不再需要程序员定义构造函数. 二.Object类主要属性 1.constructor:对象的构造函数. 2.prototype:获得类的prototype对象,static性质. 三.Object类主要方法 1.hasOwnProperty(propertyName) 判断对象是否有某个特定的属性.必须用字符串指定该属性,例如,obj.hasOwnProperty("name&q

C++ 虚基类表指针字节对齐

下面博客转载自别人的,我也是被这个问题坑了快两天了,关于各种虚基类,虚继承,虚函数以及数据成员等引发的一系列内存对齐的问题再次详细描述 先看下面这片代码.在这里我使用了一个空类K,不要被这个东西所迷惑,我使用这个空类的目的主要是为了让它产生虚基类表指针而又不引入虚基类成员变量,这样我就可以少叙述一些关于虚基类成员排放的东西,而将焦点聚集在引入的那个虚基类表指针之上.这个空类虽然有点特殊,但是在这里它其他的东西和正常的类一样,不要纠结这个.还有,代码我直接指定了对齐参数,是为了不引起混乱. #in