转载地址:http://blog.csdn.net/zuheyawen/article/details/7342118
1. 继承介绍
上一篇中,已介绍了复合类,复合类只是C++中创建复杂类的主要方法之一。
本篇中,介绍另外一种方法,即类的继承。
与复合类通过结合和连接其他对象进行创建新的对象;继承,通过直接获取其他对象的属性和行为来创建新的对象,同时对其进行扩展和特殊化。
如你继承你父母的基因;技术的更新换代;C++语言继承了C语言的许多特性,同时也增加了自己的特性等等。
父类(基类)-被继承的对象;子类(衍生类)-继承形成的对象。
继承的关系是一种is-a关系,即子类可以当作父类进行处理;
通常,子类继承了父类的所有属性,子类可以定义或重定义继承得来的属性,增加新的属性,甚至隐藏属性。
C++中需要继承的原因:
1)OOP的最基本的特性之一就是代码的重用性;但是已有代码通常不能恰好满足需要,通常的方法是修改已有代码,但是这不是最好的办法。
2)较好的方法之一,是拷贝已有的代码,然后进行修改;但这方法有许多缺点:A)很危险,因为拷贝代码会出现错漏;B)修改已有代码,你必须从根本上去理解已有的代码,如果已有代码很复杂就很困难了;C)修改和修正代码之后,必须维持原有代码的同步,维护量很大。
3)所以,继承在大多数的问题中,是一个有效的方法;继承允许你利用现有的代码,达到自己的要求。
2. C++基本继承
继承在C++中,通常出现在类之间。当一个类继承其他的类,衍生类就会继承父类的变量和函数;这些变量和函数就成为这个类的一部分。
一个Person类:
Person类包含了人的基本信息;
一个BaseballPlayer类:
BaseballPlayer也需要包含姓名、年龄和性别等信息;但刚才已定义了一个Person类,所以BaseballPlayer可以继承Person类:
现在,测试一下衍生类BaseballPlayer:
一个Emploee衍生类:
继承链:
通过继承链,我们可以定义一系列的可重用的代码。
小结:通过继承,我们可以获取父类的信息,而不用去重新定义;当父类发生改变,子类也会相应改变;
3. 衍生类的构造顺序
在上一节中,类可以从其他类中继承变量和函数;这节主要讲衍生类的构造顺序。
下面是2个新类:
Drived类包含了2部分:一部分是基类,一部分是衍生类的。
在实例化中:
Base是非衍生类,只需单单执行自己的默认构造函数;Drived是继承Base的衍生类,实例化的过程中,需执行Base和自己的构造部分;
衍生类在构造过程中,遍历继承树,构造每个继承的部分。
现在,以下演示了衍生类的构造顺序:
打印的结果是:
如以上所示,衍生类构造时,首先是构造基类部分;毕竟没有父类,不存在子类;子类使用父类中的变量和函数,而父类不了解子类,首先构造基类,保证了子类可以使用已初始化的变量。
2)继承链中的构造顺序
打印的结果是:
如上所示,继承链中的构造顺序,是居于最高层的基类首先构造,逐步沿着继承树构造。
4. 衍生类的构造函数和初始化
本章节主要详细讲解衍生类的构造函数和初始化的规则。
在上一节中:
对于Base的实例化,简单就如:
因为Base是非衍生类,只需考虑自己的成员的初始化;
构造的过程是:
A)为cBase分配内存空间;B)合适的Base构造函数调用;C)初始化列表初始化变量;D)执行构造函数;E)返回给调用者。
而对于Drived的实例化,如:
构造过程是:
A)为cDrive分配内存空间,足够承载Base部分和Drived部分;B)合适的Drived构造函数调用;C)Base对象首先构造;
D)初始化列表初始化变量;E)执行构造函数;F)返回给调用者。
衍生类和非衍生类的主要区别就是,衍生类必须首先构造基类对象。
2)初始化基类变量
在刚才的Drived的实例化中,没有提供对基类变量的初始化,如需对m_nValue和m_dValue同时赋值
新手或许会这样:
但是,C++防止了在衍生类的初始化列表中,直接对基类的变量进行初始化。即基类的变量必须在基类的初始化列表中初始化,非继承变量可以在衍生类的初始化列表初始化。
这样做的理由:假如需初始化的变量是常量,常量必须在定义时赋值;如果在构造基类完成后,再在衍生类的初始化列表中初始化,或许出现改变了常量的值。
C++这样做,使所有的变量只执行一次初始化过程。
另一种错误的做法:
在构造函数体中,执行对基类的变量赋值;这样也出现常量或引用的第二次赋值。
C++提供了选择合适的基类构造函数进行对基类的初始化。
正确的做法:
构造上顺序是:
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个类:
使用本章节的方法,BaseballPlayer如下:
测试:
小结:
衍生类的析构函数执行顺序和构造函数执行顺序相反;
通常,基类的首先执行初始化,但也给予我们选择基类的构造函数;
使用基类构造函数执行初始化,提供了代码的可维护性和减少重复性。
5. 继承与访问限制符
在以上的章节中,所涉及的数据成员都是共有的,者章节将讲述继承过程中的访问限制符。
public成员变量可以在任何对象中访问;而private成员变量只能在类的成员函数中访问;即衍生类不可以访问基类的私有成员变量。
如:
在处理继承类时,较为复杂:
1)第三个访问限制符-protected,限制访问成员函数,无论是自己的还是上一级的继承类的。
2)衍生类的访问限制符会根据继承方式的不同而改变;3种继承方式-public、private、protected,即公有/私有/保护继承。
由3种成员访问标志符和3种继承类型,构成了9种组合;
请记住以下3个基本原则:
1)类中,可以访问任何成员,不管其访问标志符;
2)对类成员的公共访问,主要基于类的访问标志符;
3)衍生类对基类成员的访问基于继承的类型,衍生类内可以对其增加的成员可以访问。
公有继承
公有继承是大多时候使用的继承类型;所有基类的成员保持其访问限制;
公有继承规则:
A)衍生类中,不可以直接访问基类的私有成员;
B)衍生类中,可以直接访问基类的保护成员;但不是将其公开化,公有继承类的对象不可以访问保护成员。
概括起来,就是公有继承中,基类的成员访问限制在衍生类中不变。
私有继承
私有继承,所有基类的成员将是作为私有成员;即基类的私有成员依然是私有,而公有和保护成员变成了私有。
私有继承,不影响衍生类对基类成员的访问,只是影响类通过衍生类对基类成员的访问限制。
保护继承
保护继承,几乎不会用到,除非是特殊的情况。
保护继承,基类的私有成员依然是私有,保护成员依然是保护,而公有的成员变成了私有的。
小结:
1)基类保持其成员的访问限制;
2)衍生类保持其对基类成员的访问限制;衍生类的继承方式不会影响该访问;
3)衍生类通过不同的继承类型,可以改变对基类成员的访问限制。
6. 在衍生类中增加、修改和隐藏成员
在以上章节中曾提到,继承的最重要之一是代码的重用;你可以在衍生类中对基类的函数进行修改和隐藏,可以增加自己的函数功能等。
1)增加新的功能
在如下的类中:
在Drived类中增加一个GetValue的函数:
2)重定义功能:
如下:
都输出一样的结果:我是一个Base。
现在,在Derived中修改基类的Identity函数:
再次测试,输出的结果是:
重定义的函数,其返回值、函数名称和返回值都必须和基类的相应的函数一致;但衍生类没有继承基类对应函数的访问限制,即衍生类可以改变重定义函数的访问限制;
3)增加现有的函数:
可以在衍生类的重定义函数中,通过::来调用基类对应的函数。
4)隐藏函数:
如上所示,衍生类可以隐藏或公开基类的某些成员;但是,衍生类不可以将基类的私有成员公有化或保护化
7. 多重继承
目前涉及的继承都是单一继承,C++提供了多重继承的功能。
例如:一个老师是一个人,同时也是一个职工;这样,老师可以同时继承人和职工。
多重继承的基类之间,使用逗号隔开。
多重继承的问题
多重继承增加了程序的复杂程度,增加了维护的困难。
1)歧义性:多重的基类包含相同名称的函数;
但是,可以通过明确的函数调用:
2)钻石问题:
因为Scanner和Printer都继承PowerDevice,Copier协调不了Scanner和Printer的功能。
大多时候,通过显示调用来解决。
通过以上2个问题,多重继承的问题都可以通过单一继承来解决;所以像Java和C#不支持多重继承,因为多重继承增加了代码程序的复杂程度。
其实,在有些情况下,多重继承是很好的解决方法。
8. 虚拟基类
在第7节中,多重继承出现的钻石问题,本章节继续该主题。
在以下:
如果实例化Copier:
因为钻石问题,会出现公有基类的构造2次:
可以通过virtual关键字,实现公有基类的一份拷贝:
当公有的基类只有一个拷贝,由Copier负责公有基类的创建;
再次测试:
需要留意:
1)虚拟基类在非虚拟基类创建之前创建;
2)继承虚拟基类的类依然是调用虚拟基类的构造函数;
3)如果一个类继承了一个或多个继承于虚拟基类的类时,最后衍生的类负责创建虚拟基类。