目录
前言
一、虚基类的作用
二、虚基类的初始化
三、例【5.9】在【例5.8】中在teacher类和student类之上增加一个共同的基类person,人员的一些基本数据放在person中
四、多层多重继承用虚基类
五、虚基类的构造函数
六、多重继承如何工作
七、虚拟继承
八、虚拟继承沙发床
九、多继承的构造顺序
十、多重继承的构造函数举例
十一、多重继承综合举例
1.方法一
(1)代码一
(2)结果一
2.方法二
(1)代码二
(2)结果二
3.方法三
(1)代码三
(2)结果三
前言
从上面的例子可知,如果一个派生类有多个直接基类,而这些直接基类有有一个共同的基类,在派生类中会保留这个间接共同基类数据成员的多个同名成员。图5.19和图5.20描述了这种情况。在引用这些同名成员时,为避免二义性,必须在派生类对象名后增加直接基类名。如:
c1.A::a=3; c1.A::display();
一、虚基类的作用
如果不希望在派生类中保留间接共同基类的多个同名成员,C++提供了虚基类的方法,使派生类在继承间接共同基类时只保留一份成员。
虚基类:用于有共同基类的场合。
声明:以virtual修饰说明基类。(class B1:virtual public B)
作用:主要用来解决多层继承时可能发生的对同一基类继承多次而产生的二义性问题;为最底层的派生类提供唯一的基类成员,而不重复产生多次拷贝。
注意:在第一级继承时就要将共同基类设计为虚基类。
声明虚基类的格式:
class 派生类名:virtual 继承方式 基类名
当基类通过多条派生路径被一个派生类继承时,派生类只继承该基类一次。
现将A类声明为虚基类:
class A
{… … };
class B: virtual public A
{… … };
class C: virtual public A
{… … };
虚基类是在声明派生类时,指定继承方式时声明的。因为一个基类可以作为一个派生类的虚基类,同时也可以作为另一个派生类的非虚基类。
派生类B和C声明虚基类后,派生类D的成员如图5.23所示。
二、虚基类的初始化
如果在虚基类中定义了带参数的构造函数,而且没定义默认构造函数,要求在它的所有派生类(直接和间接)中,通过构造函数的初始化表对虚基类进行初始化。
class A
{ A (int k) { } … … };
class B: virtual public A
{B (int n ):A(n){ }… … };
class C: virtual public A
{C (int n ):A(n){ } … … };
class D: public B,public C
{D (int n ):A(n),B(n),C(n) { } … … };
【注】
在定义类D的构造函数时,与以往的方法不同。虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。如果不由最后的派生类(如图5.21的类D)直接对虚基类初始化,而由虚基类的直接派生类(如图5.21的类B和类C)对虚基类初始化,就有可能由于在类B和类C的构造函数中对虚基类给出不同的初始化参数。所以规定:最后的派生类不仅要负责对其直接基类初始化,还要负责对虚基类初始化。
可能有人提出:类D的构造函数用初始化表调用了虚基类的构造函数A,类B和类C的构造函数也用初始化表调用了虚基类的构造函数A,这岂不是调用了三次虚基类构造函数?其实C++编译系统只执行最后的派生类调用虚基类的构造函数,忽略虚基类其他派生类(如类B和类C)调用虚基类构造函数,保证对虚基类的数据成员只作一次初始化。
三、例【5.9】在【例5.8】中在teacher类和student类之上增加一个共同的基类person,人员的一些基本数据放在person中
#include <iostream>
#include <string.h>
using namespace std;
class Person
{
public:
Person(char *nam, char s, int a)
{ // 构造函数
strcpy(name, nam);
sex = s;
age = a;
}
protected: // 保护成员
char name[20];
char sex;
int age;
};
class Teacher: virtual public Person
{ //声明Person为公用继承的虚基类
public:
Teacher(char *nam, char s, int a, char *t): Person(nam, s, a)
{ //构造函数
strcpy(title, t);
}
protected: // 保护成员
char title[10]; // 职称
};
class Student: virtual public Person
//声明Person为公用继承的虚基类
{
public:
Student(char *nam, char s, int a, float sco):
Person(nam, s, a), score(sco) { } // 初始化表
protected: // 保护成员
float score; // 成绩
};
class Graduate: public Teacher, public Student
// 声明Teacher和Student类为公用继承的直接基类
{
public:
Graduate(char *nam, char s, int a, char *t, float sco, float w): // 构造函数
Person(nam, s, a), Teacher(nam, s, a, t), Student(nam, s, a, sco),
wage(w) {}
void show( )
{ // 输出研究生的有关数据
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "score:" << score << endl;
cout << "title:" << title << endl;
cout << "wages:" << wage << endl;
}
private:
float wage; //工资
};
int main( )
{
Graduate grad1("Wang-li", 'f', 24, "assistant", 89.5, 1234.5);
grad1.show( );
return 0;
}
四、多层多重继承用虚基类
#include <iostream>
#include <string.h>
using namespace std;
class B
{
public:
B(int i )
{
b = i;
}
int b;
};
class B1 : virtual public B
{ // 在第一层声明虚基类
public:
B1(int b, int bx): B(b)
{ // 在每层派生中都调用基类
b1 = bx;
}
private:
int b1;
};
class B2 : virtual public B
{ // 在第一层声明虚基类
public:
B2(int b, int bx): B(b)
{ // 在每层派生中都调用基类
b2 = bx;
}
private:
int b2;
};
class C: public B1, public B2
{
public: // 在每层派生中都调用基类构造函数
C(int x1, int x2, int x3, int x4): B(x1), B1(x1, x2), B2(x1, x3) {
d = x4;
}
private:
int d;
};
int main(int argc, char *argv[])
{
C cc(1, 2, 3, 4);
cout << cc.b << endl;
return 0;
}
五、虚基类的构造函数
调用顺序的规则:
- 若同一层次中只包含多个虚基类,按他们声明的先后次序调用,再调用派生类的构造函数
- 若虚基类由非虚基类派生而来,先调用基类构造函数;再掉用派生类的构造函数
- 若同一层次中同时包含虚基类和非虚基类,先调用虚基类的构造函数;再掉用非虚基类的构造函数;最后调用派生类构造函数
六、多重继承如何工作
两用沙发是一张沙发,也是一张床,两用沙发允许同时继承沙发和床的特征,即SleepSofa继承Bed和Sofa两个类。
在上节中,sofa和bed都有一个weight成员这是合理的,因为两者都是实体,都有重量。问题是SleepSofa继承哪个重量?回答是两者都继承,由于两者有相同的个名字weight,使得对weight的使用变得稍微复杂一点。
假如按照下面的使用:
int main()
{
SleeperSofa ss;
ss.Setweight(20);
…
}
结果导致名称冲突(name collision),编译时出错。解决的方法是在成员名前指定其基类名:
int main()
{
SleeperSofa ss;
ss.sofa.Setweight(20);
…
}
在编写应用程序时,还要程序员知道类的层次信息,增加了编程的复杂度,在单继承中不会出现这样的问题。
七、虚拟继承
客观上讲,一个SleepSofa没有沙发和床两种重量,如此继承不是真实世界的反应。其实沙发和床都是家居的一种,凡是家具都有重量,所以通过分解考虑它们的关系。
因为SleepSofa不是直接继承Furniture,而是bed和sofa各自继承Furniture,所以完整的SleepSofa对象的内存布局如前面的图,它包括一个完整的bed,还有一个完整的sofa和SleepSofa自己的成员。它包括了两个weight成员,C++不知道SetWeight()属于哪个Furniture成员,指向Furniture的指针也不知道究竟指向哪个Furniture,这就是程序多重继承2无法通过编译的原因。
SleepSofa只需要一个Furniture,所以希望它只包含一个Furniture拷贝,同时又要共享bed和sofa的成员函数和数据成员,C++提供了虚拟继承方法实现这种继承结构。
在定义bed和sofa继承Furniture时,在冒号和继承方式之间增加关键字virtual。这相当说,如果还没有Furniture,则加入一个Furniture拷贝,否则就用已有的那下一个。此时一个SleepSofa对象在内存中只保留一个Furniture拷贝。
【注】虚拟继承的虚拟和虚拟函数的虚拟没有任何关系
八、虚拟继承沙发床
#include <iostream>
#include <string.h>
using namespace std;
class Furniture
{
protected:
int weight ;
public:
Furniture() {}
void Setweight( int i )
{
weight = i ;
}
int getweight()
{
return weight;
}
};
class bed: virtual public Furniture
{
public:
bed() {}
void Sleep()
{
cout << " 睡眠 " << endl;
}
};
class sofa: virtual public Furniture
{
public:
sofa() {}
void WatchTV()
{
cout << " 看电视 " << endl;
}
};
class SleeperSofa : public bed, public sofa
{
public:
SleeperSofa() {}
void Foldout()
{
cout << " 打开沙发" << endl;
}
};
int main(int argc, char *argv[])
{
SleeperSofa ss;
ss.Setweight(20);
Furniture *pf;
pf = &ss;
cout << pf-> getweight() << endl;
cout << ss.getweight() << endl;
return 0;
}
九、多继承的构造顺序
构造函数按下列顺序被调用:
- 按继承虚基类的顺序调用调用虚基类的构造函数
- 按继承非虚基类的顺序调用非虚基类的构造函数
- 按声明成员对象的顺序调用其构造函数
- 调用派生类自己的构造函数
十、多重继承的构造函数举例
#include <iostream>
#include <string.h>
using namespace std;
class OBJ1
{
public:
OBJ1()
{
cout << "调用OBJ1类构造函数" << endl;
}
};
class OBJ2
{
public:
OBJ2()
{
cout << "调用OBJ2类构造函数" << endl;
}
};
class Base1
{
public:
Base1()
{
cout << "调用Base1类构造函数" << endl;
}
};
class Base2
{
public:
Base2()
{
cout << "调用Base2类构造函数" << endl;
}
};
class Base3
{
public:
Base3()
{
cout << "调用Base3类构造函数" << endl;
}
};
class Base4
{
public:
Base4()
{
cout << "调用Base4类构造函数" << endl;
}
};
class Derived: public Base1, virtual public Base2,public Base3, virtual public Base4
{
public:
Derived(): Base4(), Base3(), Base2(), Base1(), obj1(), obj2()
{
cout << "调用派生类构造函数成功!" << endl;
}
protected:
OBJ1 obj1;
OBJ2 obj2;
};
int main(int argc, char *argv[])
{
Derived aa;
cout << "派生类对象 aa 构造成功,谢谢! " << endl;
return 0;
}
十一、多重继承综合举例
分别定义Teacher类和Cadre类,用多重继承方式由这两个类派生出新类Teacher_Cadre。
- 在这两个基类中都包含数据成员:姓名、年龄、性别、地址、电话
- 在Teacher类中还有职称,在Cadre类中还有职务,在派生类中有工资数据成员
- 两个基类中的数据成员用相同的名字,在引用时指定作用域
- 在类体中声明成员函数,在类体外定义成员函数
- 在派生类的成员函数show中调用Teache类的display函数,输出姓名、年龄性别、职称、地址、电话,然后再用cout语句输出职务和工资
1.方法一
(1)代码一
#include <iostream>
#include <string.h>
using namespace std;
class Teacher
{
protected:
string name;
int age;
char sex;
string title;
string addr;
string tel;
public:
Teacher(string nam, int a, char s, string tit, string ad, string t);
void display();
};
Teacher::Teacher(string nam, int a, char s,
string tit, string ad, string t): name(nam),
age(a), sex(s), title(tit),addr(ad), tel(t) { }
void Teacher::display()
{
cout << "name:" << name << endl;
cout << "age" << age << endl;
cout << "sex:" << sex << endl;
cout << "title:" << title << endl;
cout << "address:" << addr << endl;
cout << "tel:" << tel << endl;
}
class Cadre
{
protected:
string name;
int age;
char sex;
string post;
string addr;
string tel;
public:
Cadre(string nam, int a, char s, string p, string ad, string t);
void display();
};
Cadre::Cadre(string nam, int a, char s,
string p, string ad, string t):name(nam),
age(a), sex(s), post(p), addr(ad), tel(t) {}
void Cadre::display()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "post:" << post << endl;
cout << "address:" << addr << endl;
cout << "tel:" << tel << endl;
}
class Teacher_Cadre: public Teacher, public Cadre
{
private:
float wage;
public:
Teacher_Cadre(string nam, int a, char s, string tit, string p, string ad, string t, float w);
void show( );
};
//派生类构造函数
Teacher_Cadre::Teacher_Cadre(string nam,
int a, char s, string t, string p, string ad,
string tel,float w): Teacher(nam, a, s, t, ad, tel),
Cadre(nam, a, s, p, ad, tel), wage(w) {}
void Teacher_Cadre::show( )
{
Teacher::display();
cout << "post:" << Cadre::post << endl;
cout << "wages:" << wage << endl;
}
int main(int argc, char *argv[])
{
Teacher_Cadre t1("Wang-li", 50, 'f',
"prof.", "president", "135 Beijing Road,Shanghai",
"(021)61234567", 1534.5);
t1.show( );
cout << "基类Teacher长度是:" << sizeof( Teacher) << "字节" << endl;
cout << "基类Cadre长度是:" << sizeof( Cadre) << "字节" << endl;
cout << "派生类对象长度是:" << sizeof( t1) << "字节" << endl;
return 0;
}
(2)结果一
从程序运行结果得知,不采用虚基类,派生类的对象长度是各个基类长度之和。这对提高内存利用率不利。我们用虚基类的方法求解上面的题目,看派生类对象的长度怎样变化。
2.方法二
(1)代码二
#include <iostream>
#include <string.h>
using namespace std;
class Teacher
{
protected:
string name;
int age;
char sex;
string title;
string addr;
string tel;
public:
Teacher(string nam, int a,
char s, string tit, string ad, string t);
void display();
};
Teacher::Teacher(string nam, int a, char s,
string tit, string ad, string t): name(nam),
age(a), sex(s), title(tit), addr(ad), tel(t) { }
void Teacher::display()
{
cout << "name:" << name << endl;
cout << "age" << age << endl;
cout << "sex:" << sex << endl;
cout << "title:" << title << endl;
cout << "address:" << addr << endl;
cout << "tel:" << tel << endl;
}
class Cadre
{
protected:
string name;
int age;
char sex;
string post;
string addr;
string tel;
public:
Cadre(string nam, int a, char s,
string p, string ad, string t);
void display1();
};
Cadre::Cadre(string nam, int a, char s,
string p, string ad, string t):
name(nam), age(a), sex(s), post(p),
addr(ad), tel(t) {}
void Cadre::display1()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "post:" << post << endl;
cout << "address:" << addr << endl;
cout << "tel:" << tel << endl;
}
class Teacher_Cadre: virtual public Teacher, virtual public Cadre
{
public:
Teacher_Cadre(string nam, int a,
char s, string tit, string p, string ad,
string t, float w);
void show( );
private:
float wage;
};
Teacher_Cadre::Teacher_Cadre(string nam, int a,
char s, string t, string p, string ad, string tel, float w):
Teacher(nam, a, s, t, ad, tel), Cadre(nam, a, s, p, ad, tel),
wage(w) {}
void Teacher_Cadre::show( )
{
// Teacher::display();
display();
// cout<<"post:"<<Cadre::post<<endl;
cout << "post:" << post << endl;
cout << "wages:" << wage << endl;
}
int main(int argc, char *argv[])
{
Teacher_Cadre t1("Wang-li", 50, 'f',
"prof.", "president", "135 Beijing Road,Shanghai",
"(021)61234567", 1534.5);
t1.show( );
cout << "基类Teacher长度是:" << sizeof( Teacher) << "字节" << endl;
cout << "基类Cadre长度是:" << sizeof( Cadre) << "字节" << endl;
cout << "派生类对象长度是:" << sizeof( t1) << "字节" << endl;
return 0;
}
(2)结果二
从程序运行结果得知,派生类对象的长度没有变小,而是变大。
从教师类和干部类看到两个类有许多相同的数据成员,可以对两个类再抽象,建立一个基类staff,包括姓名、年龄、性别、地址、电话等数据成员。教师类从它派生,增加职称数据成员;干部类也从它派生,增加职务数据成员,教师干部类从教师类和干部类多重派生,增加工资成员。
3.方法三
(1)代码三
#include <iostream>
#include <string.h>
using namespace std;
class Staff
{
protected:
string name;
int age;
char sex;
string addr;
string tel;
public:
Staff(string nam, int a, char s, string ad, string t);
void display();
};
Staff::Staff(string nam, int a, char s, string ad, string t):
name(nam), age(a), sex(s), addr(ad), tel(t) { }
void Staff::display()
{
cout << "name:" << name << endl;
cout << "age" << age << endl;
cout << "sex:" << sex << endl;
cout << "address:" << addr << endl;
cout << "tel:" << tel << endl;
}
class Teacher: virtual public Staff
{
protected:
string title;
public:
Teacher(string nam, int a, char s,
string ad, string t, string tit);
void display();
};
Teacher::Teacher(string nam, int a, char s,
string ad, string t, string tit):
Staff(nam, a, s, ad, t), title(tit) { }
void Teacher::display()
{
Staff::display();
cout << "title:" << title << endl;
}
class Cadre: virtual public Staff
{
protected:
string post;
public:
Cadre(string nam, int a,
char s, string ad, string t, string pos);
void display1();
};
Cadre::Cadre(string nam, int a, char s,
string ad, string t, string pos):
Staff(nam, a, s, ad, t), post(pos) { }
void Cadre::display1()
{
Staff::display();
cout << "post:" << post << endl;
}
class Teacher_Cadre: public Teacher, public Cadre
{
public:
Teacher_Cadre(string nam, int a, char s,
string tit, string p, string ad, string t, float w);
void show( );
private:
float wage;
};
Teacher_Cadre::Teacher_Cadre(string nam, int a, char s, string t,
string p, string ad, string tel, float w):
Staff( nam, a, s, ad, tel), Teacher(nam, a, s, ad, tel, t),
Cadre(nam, a, s, ad, tel, p), wage(w) {}
void Teacher_Cadre::show( )
{
display();
cout << "post:" << post << endl;
cout << "wages:" << wage << endl;
}
int main(int argc, char *argv[])
{
Teacher_Cadre t1("Wang-li", 50, 'f', "prof.",
"president", "135 Beijing Road,Shanghai",
"(021)61234567", 1534.5);
t1.show( );
cout << "基类Staff长度是:" << sizeof( Staff) << "字节" << endl;
cout << "基类Teacher长度是:" << sizeof( Teacher) << "字节" << endl;
cout << "基类Cadre长度是:" << sizeof( Cadre) << "字节" << endl;
cout << "派生类教师干部对象t1长度是:" << sizeof( t1) << "字节" << endl;
return 0;
}