【C++面向对象程序设计】CH5 继承与派生(续)——虚基类

2024-02-17 22:20

本文主要是介绍【C++面向对象程序设计】CH5 继承与派生(续)——虚基类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

一、虚基类的作用

二、虚基类的初始化

三、例【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;
}

(2)结果三 


这篇关于【C++面向对象程序设计】CH5 继承与派生(续)——虚基类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/719154

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(