【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++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快