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. vs2008中使用Newtonsoft.Json

    异常:找不到方法:“Boolean System.Runtime.Serialization.DataContractAttribute.get_IsReference()” 在使用Newtonsof ...

  2. postgresql 开启远程访问

    1.如果服务器启用了防火墙,需要在防火墙上开启 5432 端口. 2.修改 PostgreSQL 配置文件 postgresql.conf.postgresql.conf,Linux 配置文件所在路径 ...

  3. 每日一“酷”之Queue

    Queue—线程安全的FIFO实现 作用:提供一个线程安全的FIFO实现 Queue模块提供了一个适用于多线程编程的先进先出(first-in,first-out)数据结构,可以用来在生产者和消费者线 ...

  4. SQL Server 本地语言版本

    要一些实验是往往喜欢使用英文的Windows 以及SQL Server ,但有时需要使用中文的环境方便理解.中文的SQL Server 不能被安装在英文的Windows 系统上. 根据文档可得知以下兼 ...

  5. Win8.1 IIS6 SQL SERVER 2012 执行 SqlServices.InstallSessionState 出错

    新装了WIN8.1,感觉很不错. 新建了第一个站点是,在执行 SqlServices.InstallSessionState("localhost", null, SessionS ...

  6. object to primitive in javascript

    例1: var a={};  alert(a); //[object Object]; 例2: var a={ toString:function(){ return 1; } } alert(a); ...

  7. MVC的Ajax的异步请求

    MVC的Ajax的异步请求 在这里小写一下MVC的异步请求的一点小总结. 个人认为是有两种的,一种就是跟webform一样的,依旧是使用jQuery的$.get()方法,只是请求地址不同,webfor ...

  8. 【BZOJ】【3404】【USACO2009 Open】Cow Digit Game又见数字游戏

    博弈论 Orz ZYF 从前往后递推……反正最大才10^6,完全可以暴力预处理每个数的状态是必胜还是必败(反正才两个后继状态),然后O(1)查询……我是SB /******************** ...

  9. 给Jquery添加alert,prompt方法,类似系统的Alert,Prompt,可以响应键盘,支持拖动

    我们在调用系统的Alert,prompt的弹出提示时,不同的系统会有不同的提示框,视觉效果不统一,而且不好看,功能单一,现在我们通过Jquery模拟Alert,prompt,现实统一视觉效果,而且内容 ...

  10. 解决ORA-00020错误

    解决ORA-00020错误 分类: Oracle2009-05-13 17:26 3398人阅读 评论(0) 收藏 举报 数据库sessionoraclesql服务器object 项目上使用的Orac ...