(一)类的三种继承方式

    类的继承方式有public(公有继承)、protected(保护继承)、private(私有继承)三种。

    公有继承,基类的公有成员和保护成员被继承到派生类中以后同样成为派生类的公有成员和保护成员,派生类中新增成员对他们可以直接访问,派生类的对象只能访问继承的基类公有成员。但是派生类的新增成员和派生类的对象都不能访问基类的私有成员。

    保护继承,基类的公有成员和保护成员被派生类继承后,变成派生类的保护成员,而基类的私有成员在派生类中不能访问。因为基类的公有成员和保护成员在派生类中都成了保护成员,所以派生类的新增成员可以直接访问基类的公有成员和保护成员,而派生类的对象不能访问它们。

    私有继承,基类的公有成员和保护成员被派生类继承后变成派生类的私有成员,而基类的私有成员在派生类中不能访问。派生类的新增成员可以直接访问基类的公有成员和保护成员,但是在类的外部通过派生类的对象不能访问它们。

    因此,可以发现,不管是保护继承还是私有继承,在派生类中成员的访问特性都是一样的。

    把上面的内容进行归纳总结:

           基类中访问权限                      继承方式                        派生类中访问权限

publicpublicpublic
protectedprotected
privateno access
publicprotectedprotected
protectedprotected
privateno access
publicprivateprivate
protectedprivate
privateno access

    1、当成员变量在基类中的访问权限为private时,不管采用哪种继承方式,在派生类中的访问权限都是no access,即不能访问。

    2、当成员变量在基类中的访问权限为protected时,

       继承方式为public时,派生类中的访问权限为protected;

       继承方式为protected时,派生类中的访问权限为protected;

       继承方式为private时,派生类中的访问权限为private。

    3、当成员变量在基类中的访问权限为public时,

       继承方式为public时,派生类中的访问权限为public;

       继承方式为protected时,派生类中的访问权限为protected;

       继承方式为private时,派生类中的访问权限为private。

如下例:

wKioL1Zb9EbBJHXZAAJBEri_xW8247.jpg

wKioL1Zb9HCClJAcAAH0rnjidhg098.jpg

分析:Derived1类通过protected继承Base类,因此Derived1的派生类A可以访问Base类的protected和public成员函数。

Derived2类通过private继承Base类,因此Derived2类本身以及Derived2的派生类B,都不可以访问Base类的任何成员。


(二)几种继承方式的关系

wKiom1ZcRjjTG01LAAAVkDw04AE473.png

wKiom1ZcRjjCIA9hAAAihQy5SKk553.png

wKioL1ZcRp3BPcjZAAAaqk9Gukk903.png

分析:上述的代码包含了三种继承方式,其实可以总结一下:

无论是公有继承、保护继承还是私有继承,派生类中新增成员都可以访问基类的公有成员、保护成员,但是,也都无法访问基类中的私有成员。

三种继承方式中,只有公有继承方式下,派生类的对象可以访问基类中的公共成员,但无法访问保护成员、私有成员。

保护继承、私有继承下,派生类的对象无法访问基类的任何成员。


(三)私有继承 和 组合

wKiom1ZcUO-DwURWAAAlLxa1SDg803.png

wKioL1ZcUVShLkLWAAAZ2XuLU1k710.png

执行结果:

wKioL1ZcWDDAvXiRAAARaR-Iqfw679.png

分析:组合是在一个类中引用另一个类,生成另一个类的实例;而继承只是继承了父类的变量和方法。

网上有一种说法,很形象,很有才:

继承是说“我父亲在家里给我帮了很大的忙”。

组合是说“我请了个老头在我家里干活”。


组合一般是将 现有的类型 作为 新类型底层实现的一部分 来加以复用,即在一个类中引用另一个类,用“has a”(有一个)表达。

继承是拥有了父类的非私有成员,即“is a”(是一个)的关系,即梧桐is a 树。

组合关系和继承关系相比,前者的最主要优势是不会破坏封装,在软件开发阶段,组合关系虽然不会比继承关系减少编码,但是到了软件维护阶段,由于组合关系使系统具有较好的松耦合性,因此使得系统更加容易维护。组合关系的缺点是比继承关系要创建更多的对象。从软件架构来说,组合,耦合度比继承弱,继承是对基类的方法和数据成员的兼收并蓄,而组合,可以有选择地使用某一种方法。

选择 组合方式 还是 继承方式,有一个清晰的办法就是,看看是否需要在新类上向基类进行向上转型,如果必须向上转型,则继承是必要的;但是如果不需要,则应该好好考虑一下是否真的需要继承。


(四)派生类的构造函数与析构函数

wKiom1ZdYO2QvZqbAAGv3HzDkG8221.jpg

wKioL1ZdYVWAXpthAAIPhMTgj2o665.jpg

wKiom1ZdYPLSlbGmAACbVwsaJhQ919.jpg

分析:Child类有3个基类,公有继承,Child类构造child对象时,先调用基类的构造函数,调用顺序应该是按照它们在派生类声明时从左到右的顺序,即第32行的顺序,Base2-->Base1-->Base3,然后再调用内嵌对象成员的构造函数,它们则也是按照声明的先后顺序,即第40~42行的顺序,b1-->b2-->b3,最后再执行Child类的构造函数。

在test()函数执行完毕后,需要释放对象,那么需要调用析构函数,析构函数执行时所有成员对象的清理顺序与构造函数的构造顺序刚好完全相反。即先执行Child类自身析构函数,然后b3-->b2-->b1,最后Base3-->Base1-->Base2。

因此执行结果如下:

wKioL1ZdYXnzJIvAAAGIt0tMiN0283.jpg


(五)作用域分辨符

如果派生类存在一个和基类某数据成员同名的数据成员,或者和基类某成员函数的名称和参数表都相同的成员函数,则派生类中的新成员就覆盖了基类成员。因此,不管在派生类内还是派生类外,都只能通过成员名访问到派生类的成员,而访问不到基类成员。

如何在派生类中访问基类中的同名成员?

可通过 基类名 和 作用域分辨符 来访问基类中的同名成员。

如下例:

wKiom1ZdhYXSbQG2AAAWxONf_UI923.png

wKioL1ZdheuCPuKtAAAoAoxv_zA347.png

分析:程序中,Child类是派生类,它是多继承,基类有Base1、Base2,由于派生类中新的成员变量x和新的成员函数show()覆盖了基类中的x和show(),因此,通过Child类生成的对象,只能访问Child类的x和show()。

如果希望用child类对象访问基类的x和show(),则需要使用 基类名+作用域分辨符,实现如程序的:

child.Base1::x = 7;

child.Base1::show();


(六)虚基类 及其派生类的构造函数
如果派生类的全部或者部分基类有共同的基类,我们将派生类直接基类的共同基类声明为虚基类后,派生类从不同的直接基类继承来的同名数据成员在内存就会只有一份拷贝,同名函数也会只有一个映射,这样不仅实现了唯一标识同名成员,而且也节省了内存空间。

如下例:

wKiom1Zdkezx3iaQAAAhSdbUiVM648.png

wKioL1ZdklKTASanAAAJIWXxNTQ772.png

输出结果为:

wKiom1ZdkuXSveVMAAAP0rx8fpg298.png


虚基类的构造函数

wKioL1ZdlUKCYQ5YAAAcclsHPA8374.png

wKioL1ZdlYCQCkVCAAAYoh5eDsA800.png

分析:可以发现Child类构造函数,不只调用了虚基类Base0的构造函数来对从它继承的成员x进行初始化,而且还调用了Base1和Base2的构造函数,这样看来,貌似从虚基类Base0继承来的成员x初始化了3次,其实不然,因为编译器会进行特殊处理,如果构造的对象中有从虚基类继承来的成员,那么虚基类成员的初始化由而且只由最远派生类的构造函数调用虚基类的构造函数来完成。除了最远派生类,它的其它基类对虚基类构造函数的调用会被忽略。

以上程序执行结果为:

wKiom1ZdlunQ8O23AAAP2O4-vV8156.png


(七)赋值兼容规则

赋值兼容规则就是指 在 基类对象 可以使用的地方都可以用 公有派生类对象 来代替。

这里有三种替代方式:

1、派生类的对象可以赋值给基类的对象。 也就是将派生类对象从基类继承的成员的值分别赋值给基类对象相应的成员。如:

base = child;

2、派生类对象的地址可以赋值给基类类型的指针。如:

pBase = &child;

3、派生类对象可以用来初始化基类的引用。如:

Base &b = child;

其实,总的来说就是,派生类对象可以给基类对象赋值。

当公有派生类对象代替基类对象使用时,即赋值兼容规则下,但是我们只能使用它从基类继承的成员,而无法使用它的新增成员。

如下例:

wKiom1Zdnb7hJjHsAAAdVgHDpoQ196.png

wKioL1ZdniTjKIF-AAAV2oEXq5k315.png

分析:定义的CallShow()函数,它的参数是基类Base类型的指针pBase,根据赋值兼容规则,我们可以使用公有派生类对象的地址为pBase赋值,因此,主函数中pBase=&ch0,pase=&ch1,都是合理的,因为ch0和ch1都是Base类的公有派生类对象。但是同时,还有一个规则,就是当使用公有派生类对象代替基类对象时,只能使用从基类继承来的成员,而无法使用派生类的新增成员。因此,不管使用哪一个派生类的对象的地址赋给pBase,都只能访问从基类Base继承过来的成员,因此输出结果为:

wKiom1Zdn0yyVgqtAAARHfnYw_4140.png