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

//例 4.15 虚基类的引例

#include<iostream>
using namespace std;
class Base{ //声明类Base1和类Base2的共同的基类Base
public:
Base()
{
a = ;
cout<<"Base Construction."<<endl;
cout<<"Base a = "<<a<<endl;
}
protected:
int a;
};
class Base1:public Base{ //声明类Base1是Base的派生类
public:
int b1;
Base1()
{
a = a+;
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+;
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 ;
}
/*
运行结果如下:
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 = ;
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+;
cout<<"Base1 Construction."<<endl;
cout<<"Base1 a = "<<a<<endl;
}
};
class Base2:virtual public Base{ //声明类Base是Base2的虚基类
public:
int b2;
Base2()
{
a = a+;
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 ;
}
/*
运行结果如下:
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(,,,);
return ;
}

发现:
在上述程序中,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("张大明",'男',,"计算机",,"教务处",);
obj.print();
return ;
}

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

  1. 【C++】继承(虚基类)

    类的继承与派生 面向对象技术强调软件的可重用性,这种重用性通过继承机制来实现.而在类的继承过程中,被重用的原有类称为基类,新创建的类称为派生类.派生类定义语法格式如下: class <派生类名& ...

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

    当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射.可以使用作用 ...

  3. 【c++】虚基类

    何要使用虚基类: 为何避免多层继承中出项多个公共基类所造成的歧义现象 虚基类用法 派生类继承基类时,加上一个virtual关键词则为虚拟基类继承. 在上图程序运行中,我们发现class bass的构造 ...

  4. C++ - 虚基类、虚函数与纯虚函数

    虚基类       在说明其作用前先看一段代码 class A{public:    int iValue;}; class B:public A{public:    void bPrintf(){ ...

  5. 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类

    [源码下载] 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 多重继承 虚基类 示例1 ...

  6. C++学习20 虚基类详解

    多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类 ...

  7. 一目了然c++虚基类!

    #include <IOSTREAM.H> //基类 class CBase ...{ protected: int a; public: CBase(int na) ...{ a=na; ...

  8. 【c++内存分布系列】虚基类表

    虚基类表相对于虚函数表要稍微难理解些,故单独提出来. 虚函数表是在对象生成时插入一个虚函数指针,指向虚函数表,这个表中所列就是虚函数. 虚基类表原理与虚函数表类似,不过虚基类表的内容有所不同.表的第一 ...

  9. C++ (P160—)多继承 二义性 虚基类 “向上转型”

    1 多继承中,必须给每个基类指定一种派生类型,如果缺省,相应的基类则取私有派生类型,而不是和前一个基类取相同的派生类型 2 一个类的保护成员只能被本类的成员函数或者它的派生类成员函数访问 3 由于c+ ...

随机推荐

  1. mercurial(hg)使用

    # 版本管理软件的比较 svn 每个目录下建一个.svn目录实在是不爽. git 分支管理非常方便,但没感觉有什么用,主要还是在修改前提交一次代码, 等后悔时再回来,没什么其他的目的.关键是中文乱码问 ...

  2. 【转】Linux Framebuffer

    全面的framebuffer详解 一.FrameBuffer的原理 FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口. Linux是工作在保护模式下,所以用户态进程是无法象D ...

  3. XAML 概述四

    这一节我们来简单介绍一下XAML的加载和编译,它包括如下三种方式:  · 只使用代码  · 使用代码和未编译的XAML  · 使用代码和编译过的BAML 一. 只使用代码 我们首先创建一个简单的控制台 ...

  4. Microsoft server software support for Microsoft Azure virtual machines

    http://support.microsoft.com/kb/2721672/en-us  Article ID: 2721672 - Last Review: November 22, 2014 ...

  5. Eclipse中使用javap运行配置详解

    javap是sun提供的对class文件进行反编译的工具 1.配置Run---external tools---external tools configurations 选择Program 新建ja ...

  6. Java中main函数参数String args[] 和 String[] args 区别

    其实没什么区别的:当初我也是这样的疑问,呵呵:非要说区别就看下面:执行效果上没有不同, 但在语法意义上略有不同. 比如, String与String[], 前者叫字符串类型而后者叫字符串数组类型. S ...

  7. 8种方法提升windows 8使用方便-----Win+x 编辑菜单

    在windows 8上,你可以同时按下windows键和x键或者右键点击屏幕左下角打开一个菜单名为电源菜单或者快速访问菜单,这个菜单包含快速访问系统的工具,如控制面板,命令提示符,任务管理器,资源管理 ...

  8. Document.defaultView

    Document.defaultView 引子 最近愚安在写一个可以将页面上的资源链接转为二维码以方便移动端浏览的chrome插件,由于dom操作并不多,而且作为插件不需要考虑跨 浏览器兼容性,所以并 ...

  9. java,图片压缩,略缩图

    在网上找了两个图片的缩放类,在这里分享一下: package manager.util; import java.util.Calendar; import java.io.File; import ...

  10. The 10th Zhejiang Provincial Collegiate Programming Contest

    Applications http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5008 string set 专场 #include& ...