C++学习第12篇-类的继承

时间:2022-09-13 18:22:19

1. 继承介绍

上一篇中,已介绍了复合类,复合类只是C++中创建复杂类的主要方法之一。

本篇中,介绍另外一种方法,即类的继承

与复合类通过结合和连接其他对象进行创建新的对象;继承,通过直接获取其他对象的属性和行为来创建新的对象,同时对其进行扩展和特殊化。

如你继承你父母的基因;技术的更新换代;C++语言继承了C语言的许多特性,同时也增加了自己的特性等等。

父类(基类)-被继承的对象;子类(衍生类)-继承形成的对象。

继承的关系是一种is-a关系,即子类可以当作父类进行处理;

通常,子类继承了父类的所有属性,子类可以定义或重定义继承得来的属性,增加新的属性,甚至隐藏属性。

C++中需要继承的原因:

1)OOP的最基本的特性之一就是代码的重用性;但是已有代码通常不能恰好满足需要,通常的方法是修改已有代码,但是这不是最好的办法。

2)较好的方法之一,是拷贝已有的代码,然后进行修改;但这方法有许多缺点:A)很危险,因为拷贝代码会出现错漏;B)修改已有代码,你必须从根本上去理解已有的代码,如果已有代码很复杂就很困难了;C)修改和修正代码之后,必须维持原有代码的同步,维护量很大。

3)所以,继承在大多数的问题中,是一个有效的方法;继承允许你利用现有的代码,达到自己的要求。

2. C++基本继承

继承在C++中,通常出现在类之间。当一个类继承其他的类,衍生类就会继承父类的变量和函数;这些变量和函数就成为这个类的一部分。

一个Person类:

C++学习第12篇-类的继承

Person类包含了人的基本信息;

一个BaseballPlayer类:

C++学习第12篇-类的继承

BaseballPlayer也需要包含姓名、年龄和性别等信息;但刚才已定义了一个Person类,所以BaseballPlayer可以继承Person类:

C++学习第12篇-类的继承

现在,测试一下衍生类BaseballPlayer:

C++学习第12篇-类的继承

一个Emploee衍生类:

C++学习第12篇-类的继承

继承链:

C++学习第12篇-类的继承

通过继承链,我们可以定义一系列的可重用的代码。

小结:通过继承,我们可以获取父类的信息,而不用去重新定义;当父类发生改变,子类也会相应改变;

3. 衍生类的构造顺序

在上一节中,类可以从其他类中继承变量和函数;这节主要讲衍生类的构造顺序。

下面是2个新类:

C++学习第12篇-类的继承

Drived类包含了2部分:一部分是基类,一部分是衍生类的。

在实例化中:

C++学习第12篇-类的继承

Base是非衍生类,只需单单执行自己的默认构造函数;Drived是继承Base的衍生类,实例化的过程中,需执行Base和自己的构造部分;

衍生类在构造过程中,遍历继承树,构造每个继承的部分。

现在,以下演示了衍生类的构造顺序:

C++学习第12篇-类的继承

打印的结果是:

C++学习第12篇-类的继承

如以上所示,衍生类构造时,首先是构造基类部分;毕竟没有父类,不存在子类;子类使用父类中的变量和函数,而父类不了解子类,首先构造基类,保证了子类可以使用已初始化的变量。

2)继承链中的构造顺序

C++学习第12篇-类的继承

打印的结果是:

C++学习第12篇-类的继承

如上所示,继承链中的构造顺序,是居于最高层的基类首先构造,逐步沿着继承树构造。

4. 衍生类的构造函数和初始化

本章节主要详细讲解衍生类的构造函数和初始化的规则。

在上一节中:

C++学习第12篇-类的继承

对于Base的实例化,简单就如:

C++学习第12篇-类的继承

因为Base是非衍生类,只需考虑自己的成员的初始化;

构造的过程是:

A)为cBase分配内存空间;B)合适的Base构造函数调用;C)初始化列表初始化变量;D)执行构造函数;E)返回给调用者。

而对于Drived的实例化,如:

C++学习第12篇-类的继承

构造过程是:

A)为cDrive分配内存空间,足够承载Base部分和Drived部分;B)合适的Drived构造函数调用;C)Base对象首先构造;

D)初始化列表初始化变量;E)执行构造函数;F)返回给调用者。

衍生类和非衍生类的主要区别就是,衍生类必须首先构造基类对象。

2)初始化基类变量

在刚才的Drived的实例化中,没有提供对基类变量的初始化,如需对m_nValue和m_dValue同时赋值

新手或许会这样:

C++学习第12篇-类的继承

但是,C++防止了在衍生类的初始化列表中,直接对基类的变量进行初始化。即基类的变量必须在基类的初始化列表中初始化,非继承变量可以在衍生类的初始化列表初始化。

这样做的理由:假如需初始化的变量是常量,常量必须在定义时赋值;如果在构造基类完成后,再在衍生类的初始化列表中初始化,或许出现改变了常量的值。

C++这样做,使所有的变量只执行一次初始化过程。

另一种错误的做法:

C++学习第12篇-类的继承

在构造函数体中,执行对基类的变量赋值;这样也出现常量或引用的第二次赋值。

C++提供了选择合适的基类构造函数进行对基类的初始化。

正确的做法:

C++学习第12篇-类的继承

构造上顺序是:

A)cDrive分配内存空间;C)Drived(double,int)构造函数调用,dValue=5.0, nValue=5;D)基类构造函数Base(int)调用,m_nValue=5 ;

E)基类构造函数执行返回;F)衍生类将其m_dValue=5.0 ;G)衍生类构函数执行返回。

现在,再次回顾一下2个类:

C++学习第12篇-类的继承

使用本章节的方法,BaseballPlayer如下:

C++学习第12篇-类的继承

测试:

C++学习第12篇-类的继承

小结:

衍生类的析构函数执行顺序和构造函数执行顺序相反;

通常,基类的首先执行初始化,但也给予我们选择基类的构造函数;

使用基类构造函数执行初始化,提供了代码的可维护性和减少重复性。

5. 继承与访问限制符

在以上的章节中,所涉及的数据成员都是共有的,者章节将讲述继承过程中的访问限制符。

public成员变量可以在任何对象中访问;而private成员变量只能在类的成员函数中访问;即衍生类不可以访问基类的私有成员变量。

如:

C++学习第12篇-类的继承

在处理继承类时,较为复杂:

1)第三个访问限制符-protected,限制访问成员函数,无论是自己的还是上一级的继承类的。

C++学习第12篇-类的继承

2)衍生类的访问限制符会根据继承方式的不同而改变;3种继承方式-public、private、protected,即公有/私有/保护继承。

C++学习第12篇-类的继承

由3种成员访问标志符和3种继承类型,构成了9种组合;

请记住以下3个基本原则:

1)类中,可以访问任何成员,不管其访问标志符;

2)对类成员的公共访问,主要基于类的访问标志符;

3)衍生类对基类成员的访问基于继承的类型,衍生类内可以对其增加的成员可以访问。

公有继承

公有继承是大多时候使用的继承类型;所有基类的成员保持其访问限制;

C++学习第12篇-类的继承

公有继承规则:

A)衍生类中,不可以直接访问基类的私有成员;

B)衍生类中,可以直接访问基类的保护成员;但不是将其公开化,公有继承类的对象不可以访问保护成员。

概括起来,就是公有继承中,基类的成员访问限制在衍生类中不变。

私有继承

私有继承,所有基类的成员将是作为私有成员;即基类的私有成员依然是私有,而公有和保护成员变成了私有。

私有继承,不影响衍生类对基类成员的访问,只是影响类通过衍生类对基类成员的访问限制。

C++学习第12篇-类的继承

保护继承

保护继承,几乎不会用到,除非是特殊的情况。

保护继承,基类的私有成员依然是私有,保护成员依然是保护,而公有的成员变成了私有的。

C++学习第12篇-类的继承

小结:

1)基类保持其成员的访问限制;

2)衍生类保持其对基类成员的访问限制;衍生类的继承方式不会影响该访问;

3)衍生类通过不同的继承类型,可以改变对基类成员的访问限制。

6. 在衍生类中增加、修改和隐藏成员

在以上章节中曾提到,继承的最重要之一是代码的重用;你可以在衍生类中对基类的函数进行修改和隐藏,可以增加自己的函数功能等。

1)增加新的功能

在如下的类中:

C++学习第12篇-类的继承

在Drived类中增加一个GetValue的函数:

C++学习第12篇-类的继承

2)重定义功能:

如下:

C++学习第12篇-类的继承

都输出一样的结果:我是一个Base。

现在,在Derived中修改基类的Identity函数:

C++学习第12篇-类的继承

再次测试,输出的结果是:

C++学习第12篇-类的继承

重定义的函数,其返回值、函数名称和返回值都必须和基类的相应的函数一致;但衍生类没有继承基类对应函数的访问限制,即衍生类可以改变重定义函数的访问限制;

3)增加现有的函数:

C++学习第12篇-类的继承

可以在衍生类的重定义函数中,通过::来调用基类对应的函数。

4)隐藏函数:

C++学习第12篇-类的继承

如上所示,衍生类可以隐藏或公开基类的某些成员;但是,衍生类不可以将基类的私有成员公有化或保护化

7. 多重继承

目前涉及的继承都是单一继承,C++提供了多重继承的功能。

例如:一个老师是一个人,同时也是一个职工;这样,老师可以同时继承人和职工。

多重继承的基类之间,使用逗号隔开。

C++学习第12篇-类的继承

C++学习第12篇-类的继承

多重继承的问题

多重继承增加了程序的复杂程度,增加了维护的困难。

1)歧义性:多重的基类包含相同名称的函数;

C++学习第12篇-类的继承

但是,可以通过明确的函数调用:

2)钻石问题:

C++学习第12篇-类的继承

因为Scanner和Printer都继承PowerDevice,Copier协调不了Scanner和Printer的功能。

大多时候,通过显示调用来解决。

通过以上2个问题,多重继承的问题都可以通过单一继承来解决;所以像Java和C#不支持多重继承,因为多重继承增加了代码程序的复杂程度。

其实,在有些情况下,多重继承是很好的解决方法。

8. 虚拟基类

在第7节中,多重继承出现的钻石问题,本章节继续该主题。

在以下:

C++学习第12篇-类的继承

如果实例化Copier:

C++学习第12篇-类的继承

因为钻石问题,会出现公有基类的构造2次:

C++学习第12篇-类的继承

可以通过virtual关键字,实现公有基类的一份拷贝:

C++学习第12篇-类的继承

当公有的基类只有一个拷贝,由Copier负责公有基类的创建;

C++学习第12篇-类的继承

再次测试:

C++学习第12篇-类的继承

需要留意:

1)虚拟基类在非虚拟基类创建之前创建;

2)继承虚拟基类的类依然是调用虚拟基类的构造函数;

3)如果一个类继承了一个或多个继承于虚拟基类的类时,最后衍生的类负责创建虚拟基类。


【免责特此声明:
1)本内容可能是来自互联网的,或经过本人整理的,仅仅代表了互联网和个人的意见和看法!
2)本内容仅仅提供参考,任何参考该内容造成任何的后果,均与原创作者和本博客作者无关!】