面向对象的三大特性之继承的深入浅出

2017-02-28 15:13 阅读 831 次 评论 1 条

继承是面向对象程序设计的一个重要特性。可以说,如果没有掌握继承,就等于没有掌握类和对象的精华,就是没有掌握面向对象横须设计的真谛。继承可以在已有的类的基础上创建新的类,新类可以从一个或多个已有类中继承成员函数和数据成员,而且可以重新定义或加进新的数据和函数,从而引成类的层次或登记,其中,已有类称为基类或父类,在它基础上建立的新类称为派生类或子类。

什么是继承?为什么要引入继承?

通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。使用继承,可以在一定程度上忽略相似类型的区别,而用统一的方式使用它们的对象。C++中继承引入的最终目的就是为了实现代码的复用。

继承的基本性质

①声明一个派生类的一般格式:

说明:"基类名"是一个已经声明的类的名称,"派生类名"是继承原有类的特性而生成的新类的名称。"继承方式"规定了如何访问从基类继承的成员,关键字private、protected、public。如果不显示的给出继承方式,系统默认为私有继承(private)。

②继承方式:public(公有继承)、private(私有继承)、protected(保护继承)

公有继承:基类中公有成员和保护成员在派生类的访问权限不发生改变,基类中的私有成员在派生类中不可访问。

保护继承:基类中的公有成员和保护成员在派生类中都被修改为保护成员,基类中的私有成员在派生类中不可访问。

私有继承:基类中的公有成员和保护成员在派生类中被修改为私有成员,基类中的私有成员在派生类中不可访问。

③继承方式的总结

⒈积累的private成员在派生类中是不可访问的,如果基类成员不想在类外直接被访问,但需要派生类中能访问,可以定义为protected。可以看出保护成员限定符是因继承才出现的。

⒉public继承是一个接口继承,保持is-a的原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。

⒊protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据....实现的)。通常比组合更低级,但当一个派生类需要访问基类保护成员或需要重定义类的虚函数时它就是合理的。

⒋不管哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不可访问)。

⒌使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

⒍在实际运用中一般使用的都是public继承,极少场景下才会使用protected和private继承。

④派生类的对象模型

派生类构造函数和析构函数的调用顺序

通常情况下,当创建派生类对象时,首先调用基类的构造函数,随后再调用派生类的构造函数。当撤销派生类对象时,则先调用派生类的析构函数,随后在调用基类的析构函数。运用一个简单的例子加以说明:

①单层派生中构造函数与析构函数的调用顺序

结论:在定义对象时,系统会自动调用构造函数,而撤销对象时则会调用析构函数。构造函数的调用严格遵照先调用基类的构造函数,在调用派生类的构造函数的顺序。析构函数的调用顺序与其恰好相反,先调用派生类的析构函数,在调用基类的析构函数。

②多层派生类的构造和析构函数调用顺序

通过上面的例子可以清楚地看到多层派生时的构造函数和析构函数依次被调用的顺序。在多层派生的情况下需要从底层开始执行构造函数,撤销对象时,则从高层撤销对象。

说明:①基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。②基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。③基类定义了带有形参构造函数,派生类就一定定义构造函数。

同名隐藏机制

继承体系中的作用域:①在继承体系中基类和派生类是两个不同的作用域 ②子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问(在子类成员中可使用基类 :: 基类成员 访问)。③注意在实际中在继承体系里面最好不要定义同名的成员。比如以下程序中,当子类与父类有同名成员时,通过类名+作用域表示符进行访问即可:

注意:当基类与派生类成员函数同名时,即使参数列表不同,依旧不会构成重载,因为两个成员函数明显处于不同的作用域当中。

赋值兼容规则

①子类对象可以赋值给父类对象(切割/切片)

②父类对象不能赋值给子类对象

③父类的指针/引用可以指向子类对象

④子类的指针/引用不能指向父类对象(但可以通过强制类型转换完成)

友元关系与继承

友元关系不能继承,也就是说基类友元不能访问子类的私有和保护成员。很重要的一个原因就是,友元不是类的成员函数,无法继承于派生类。

继承与静态成员

打印结果不难看出,定义派生类对象,其对象的静态成员变量也会相应增加,子类也可以修改静态成员变量。基类定义的static成员,则整个继承体系中只有一个这样的成员。无论派生多少个子类,都只有一个static成员,即静态成员为所有类成员所共享。

单继承特性

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

多继承特性

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

菱形继承特性

菱形继承也称为钻石继承,一个基类有两个派生类来继承,然后派生类的派生类继承基类的两个子类。无疑,菱形继承的引入会造成严重的二义性以及数据冗余的问题。

虚继承的引入

虚继承的引入无疑是为了解决菱形继承带来的二义性和数据冗余的问题。

①虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和空间浪费的问题。

②虚继承体系看起来很复杂,在实际应用中我们通常不会定义如此复杂的继承体系。因为使用虚继承解决数据冗余问题的同时也带来了性能上的损耗。

下面我们通过一个例子来说明一下虚基类的具体作用:

由打印结果不难看出,从基类Base派生出Base1和Base2时,使用关键字virtual,把类Base声明为Base1和Base2的虚基类。这样一来,从类Base1和类Base2派生来的Dervice只会继承Base一次,也就是说,基类Base的成员a只保留了一份。

下面再来看一个程序,虚基类的引用究竟发生了哪些变化?

最后我用一个例子来求证一下多虚继承的对象模型以及所占字节大小:

虚继承的注意事项

①即使基类是虚基类,也能通过基类的指针或引用操作派生类的对象。

②虚继承只是解决了一个派生类对象中存在同一个基类的多份拷贝的问题,并没有解决多个基类存在同名成员的二义性问题。

③在虚继承中,虚基类是有最低曾的派生类初始化的。

④含有虚基类的对象的构造函数与一般的多重继承的构造函数稍微有点区别:先初始化虚基类子对象(最低层派生类负责),然后按派生列表中的顺序依次对直接基类(非虚基类)初始化。

⑤析构函数的调用顺序与构造函数调用顺序恰好相反。

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:面向对象的三大特性之继承的深入浅出 | 术与道的分享
分类:编程素养 标签:
1024do.com导航_术与道导航平台

发表评论


表情

  1. alanxgorlan
    alanxgorlan 【农民】 @回复

    老哥稳,咋不分享到群里,很强的,第三个代码块有乱码。