【C++】继承(下) 单继承 | 多继承 | 菱形继承 | 继承和组合

一、单/多/菱形继承

1.单继承

当一个子类只有一个直接父类时,称这个继承关系为单继承。

2.多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承。

举个实例:新老师进学校工作时,一般会作为助教老师,一边代课教书,一边跟着经验足的老教师后头 学习一阵子。这时我们定义出的"Assistant"类,就同时具有老师、学生这两种属性。这就是多继承的思想。

多继承的书写格式为:逗号+继承方式+父类名

3.菱形继承

是多继承的一种特殊情况。

a.产生的问题

这种继承结构会导致二义性 以及空间浪费等问题。

什么叫产生二义性?我用上面的例子解释给你听:

class Person
{
public:
    Person(string str="")
        :_name(str)
    {}
    string _name="";
};
​
class Student : public Person  //继承了person
{
public:
    Student()
        :Person("student")
    {}
    int _num=0;
};
​
class Teacher : public Person   //继承了person
{
public:
    Teacher()
        :Person("teacher")
    {}
    int _id=0;
};
 
class Assistant :public Student, public Teacher   //继承的这俩,都是person的派生类
{};
int main() {
    Assistant a;
    cout << a._name << endl;
    return 0;
}

这样写,编译是无法通过的:

这是因为此时的a里面,有两个_name,编译器不知道用哪个了:

如果还是不理解,可以看这张图:

这就产生了二义性。并且,由于Assistant中有两份 _name的拷贝,当 _name要用的空间很大的话,就会造成空间浪费。

b.如何解决

那遇到菱形继承的情况,要怎么解决二义性和数据冗余的问题呢?

Way1. 显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决。

cout << a.Student::_name << endl;
cout << a.Teacher::_name << endl;

Way2. 虚拟继承

先来介绍下虚拟继承:虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类。

怎么设为虚继承呢?在继承方式前加上virtual关键字即可。

(注意:虚拟继承是专门用于处理 菱形继承 的手段,不要在其他地方去使用)

class Person
{
public:
    Person(string str="")
        :_name(str)
    {}
    string _name="";
};
​
class Student :virtual public Person   //虚继承
{
public:
    Student()
        :Person("student")
    {}
    int _num=0;
};
​
class Teacher :virtual public Person   //这俩都设为虚继承
{
public:
    Teacher()
        :Person("teacher")
    {}
    int _id=0;
};
​
class Assistant :public Student, public Teacher
{};
int main() {
    Assistant a;
    cout << a._name << endl;
    return 0;
}

这里编译器做了优化处理,看似有3个Person,实际上只有一个,这仨都是同一个:

虚继承使得从不同路径继承来的同名基类,在派生类中只产生一个实例,避免了二义性问题。

4.劝告

一般不建议设计出多继承,并且,如果不是迫不得已,不要设计出菱形继承!否则在复杂度及性能上容易出问题。

多继承可以认为是C++的缺陷之一,很多后来的语言都没有多继承,如Java。

二、继承和组合

继承与组合都是用于描述类之间的关联关系的。

继承:继承是一种"is-a"的关系,表示一个类从另一个类派生而来,每个派生类对象都是一个基类对象。

组合:组合是一种"has-a"的关系,表示一个类包含另一个类的对象作为成员变量。通过组合,一个类可以使用另一个类的功能,但不会继承其属性和方法。

在不同的情境下,俩类之间设为继承关系还是组合关系好呢?下面用例子来说明。

//继承
class Car{
    ……
};
​
class BMW : public Car{   //宝马is a car,这俩构成继承关系
    ……
};
//组合
class Tire{
    ……
};
​
class Car{   //car has a tire,这俩构成组合关系
    Tire _t;
    ……
};  

通过这俩例子,可见用继承还是组合,得去判断是"is a"还是"has a",如果前者,就用继承;后者就用组合;两个都行,那就优先用组合。优先使用组合,而不是继承。

这里说明下 优先用组合 的原因:

继承是一种白箱复用。所谓白箱复用,就是透明可视化的一种复用,父类的内部细节对子类可见。这在一定程度上破坏了父类的封装。

并且,父类和子类的依赖关系很强,耦合度很高。试想,假如父类的某个成员被修改了,那在所有的子类中也会遭到修改。

而组合是一种黑箱复用。黑箱复用是另一种复用风格:新的更复杂的功能可以通过组合对象来获得。这要求被组合的对象具有良好定义的接口。派生类直接拿接口来用,而不涉及它的内部实现,这保护了基类的封装性。

并且,耦合度低,代码维护性好,我修改基类的某个成员,子类并不会受影响。

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验题目1:班级学生学期成绩管理系统 (1)程序功能简介 灵活运用类的继承、对象成员等机制,设计一个能够实现班级学生学期成绩管理的程序。 (2)程序设计说明 ① 个人信息类CPerson的数据成员有姓名、性别、年龄、身份证号等数据成员,成员函数根据需要自行设计; ② 学生类CStudent从CPerson派生,并增加学号、CCourse对象成员数组(大小至少3个)等数据成员,并根据需要自行设计成员函数,包括能够求解所选修课程的总学分、显示某个学生基本信息和课程信息的成员函数; ③ 课程类CCourse包括课程名、学分、分数、任课老师等数据成员,成员函数根据需要自行设计; ④ 班级类CClass的数据成员有班级名称、班级人数、CStudent对象成员数组(大小由构造函数确定)等。本班级类CClass的对象成员数组需要在构造函数用new动态分配内存空间,在析构函数用delete释放。在CClass类设计包括能够求解最高成绩、最低成绩和平均成绩以及通过学号查找并输出某个学生全部信息(例如Seek())等功能在内的成员函数; ⑤ 构造三十个学生的数据,每个学生都有三门课程成绩,输出并显示这些数据; ⑥ 根据类的需要添加适当的其它成员,编写完整的程序并测试。 (3)程序调试运行 运行程序查看结果,并进行源代码调试和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值