【C++面向对象程序设计】CH5 继承与派生(续)——虚基类
创始人
2024-02-28 06:02:49
0

目录

前言

一、虚基类的作用

二、虚基类的初始化

三、例【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 
#include 
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 
#include 
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 
#include 
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 
#include 
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 
#include 
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 
#include 
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:"<

(2)结果二 

        从程序运行结果得知,派生类对象的长度没有变小,而是变大。
        从教师类和干部类看到两个类有许多相同的数据成员,可以对两个类再抽象,建立一个基类staff,包括姓名、年龄、性别、地址、电话等数据成员。教师类从它派生,增加职称数据成员;干部类也从它派生,增加职务数据成员,教师干部类从教师类和干部类多重派生,增加工资成员。

3.方法三

(1)代码三

#include 
#include 
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;
}

(2)结果三 


相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...