C++继承(2)

发布于:2024-07-01 ⋅ 阅读:(13) ⋅ 点赞:(0)

目录

1.如何实现一个不可以被继承的类

2.继承和友元函数

3.继承和静态成员变量

4.菱形(虚拟)继承

(1)单继承和多继承

(2)菱形继承

(3)菱形继承的特殊情况

(4)虚继承机理分析

5.继承和组合

6.实战演练


1.如何实现一个不可以被继承的类

(1)我们上一次介绍了这个相关的构造函数和析构函数的派生类的诸多使用注意事项,我们如何实现一个不可以被继承的类呢,下面介绍两个方式;

(2)第一个就是使用的我们上次介绍的这个知识,我们可以把这个父类的构造函数定义为是私有的,这样的话,我们的这个类就是不可以被继承的,具体而言就会体现在这个我们在主函数里面使用这个类创建对象的时候就会报错;

(3)上面介绍的就是一种直接的方式,我们下面介绍一种间接的不可以被继承的方式,就是使用的C++11里面提供的一个关键字final,这个就很直接了,因为这个时候只要我们继承这个父类,这个时候编译器就会报错;

2.继承和友元函数

(1)这个地方只需要记住一句话,就是友元函数不可以被继承,如果子类里面也想要使用这个友元函数,我们就需要把这个函数定义为子类的友元,这个也是有办法解决的;

(2)友元函数不属于任何一个类,但是在友元函数里面,我们可以访问类里面的私有成员变量;如果我们不添加上子类里面的友元,这个时候我们的s是没有办法去访问受保护的成员变量的;在这个程序里面,我们可以把这个private理解成为和protected相同的意思;

(3)当我们在子类里面不添加友元函数的时候,我们没有办法去访问这个受保护的成员,这个地方也是可以证明我们的友元函数是不可以被继承的;

3.继承和静态成员变量

(1)静态成员变量在这个派生类里面是可以直接使用的,但是这两个是属于同一份的;

(2)实际上这个静态成员变量不属于对象,而是属于整个类域的,这个地方只是继承了它的使用权,静态成员变量和静态成员函数只要突破类域,我们就可以进行访问了;两个的地址是一样的,并不能说子类继承了父类的静态成员变量,但是这个子类也可以去使用静态成员变量;

(3)上面的这个案例就是使用静态的成员函数,以及这个继承的相关规则,去设计了一可以判断创建对象个数的程序,我们去创建父类对象的时候,静态成员变量就会count++,我们在创建子类对象的时候,同样去调用父类的构造函数,因为这个继承的时候,子类里面继承父类的成员变量需要使用父类的构造函数进行初始化;

4.菱形(虚拟)继承

(1)单继承和多继承

单继承就是只有一个直接父类的继承关系;

多继承就是一个类有两个及以上的直接父类的继承关系;

(2)菱形继承

菱形继承是有问题的,是属于这个多继承的一种特殊的情况,因为在这个下面的菱形继承里面,我们的assistant子类里面,有两份这个person的数据,一份是从student里面获得的,还有一份就是从teacher里面获得的;

这样的话就造成了数据的二义性,当我们进行访问的时候,编译器不知道我们想要访问那一份数据,这个就造成了数据的冗余和二义性,我们的解决办法就是使用虚拟继承,简称虚继承;

(3)菱形继承的特殊情况

上面的多继承需要在这个student类和teacher类里面使用virtual关键字,表示这个继承是一个虚拟继承,右边的这个需要把virtual添加在左边的中介,和右边的上面的那个类里面去;

右边的这个也是属于菱形继承的,只不过多了一层,我们可以把这个视为菱形继承,这个时候的virtual我们记住就是添加在和父类直接相连接的类上面,这样才可以解决二义性和数据冗余的问题;

(4)虚继承机理分析

我们是以下面的这个菱形继承作为案例进行分析的,这个D是子类,继承了BC类,A又被BC继承

实际上这个虚拟继承干的事情就是把这个A里面的数据单独的放到某一个位置里面去,我们想要访问这个数据的时候,是通过内存里面的一个偏移量的表去查看的,这个表里面表示出来了这个BC和A的偏移量的信息;我们可以通过这个偏移量找到这个相关的A里面的数据;

这个里面可能有些同学会说这个地方为什么不存储指针,通过指针我们也可以直接找到这个A里面的数据啊,为什么还要多此一举,搞出来一张偏移量表呢,实际上,这个表里面除了存储这个偏移量,还会存储一些其他的信息,这个信息如果全放在这个内存李敏就会很消耗空间吧;

总之,就是这个虚继承原理就是搞一张偏移量的表,我们通过这个表查找BC两个子类和A里面的数据的偏移量,而这个A里面的数据是被直接放在了其他的区域里面去的;

5.继承和组合

(1)继承我们上面已经介绍的很清楚了,组合就是在一个类里面去使用另外的一个类去定义对象,这个就是组合;

 

(2)上面的这段文字里面有这个黑箱和白箱,我们简单的说明一下,什么是黑箱,什么是白箱,黑箱就是指的这个里面的机理我们不是很清楚,我们只是凭借自己的认识和经验去揣测,白箱就是这个里面的机理我们是很清楚的,使用的什么语法,什么语句,我们是一清二楚的;

(3)具体到这个继承和组合上面而言,这个继承的类之间的关系就很清楚了,什么继承方式这个表示的就是很明显的,但是这个组合,我们在一个类里面去定义另外的一个类,但是至于两个类之间是什么关系,我们是很难去精确的描述的;

(4)继承的耦合度高,组合的耦合度低,这个耦合度指的就是不同的类,不同的模块之间的关联程度,我们从这个代码的实现上面去看,这个继承好像就会好一些,但是实际上,这个组合会更好,因为这个组合的模块之间的耦合度是很低的,就是说这个模块之间的关联性不是很强,我们对于某一个模块进行修改的时候,另外一个模块受到影响的可能性就会比较小;

(5)我们从软件工程的角度来讲,我们追求的是高内聚,低耦合,内聚就是这个每一个模块内部的这个代码,以及这个实现的过程是很健壮的,就是这个功能很全面,不会让两个模块之间的相似性很大,内聚就是做到泾渭分明,你实现你的功能,我实现我的功能,而且我们自己内部这个功能的实现是很成熟的,但是我们不会让这个耦合度很高;

但是这个并不意味着继承没有用,当这个程序里面的不同模块之间具有关联性的时候,这个继承还是有自己的用途的,只不过在这个两者皆可使用的情况下,我们会优先考虑使用组合的方式;

6.实战演练

(1)这个问题就是一个单继承问题

(2)我们画出来这个D对象的模型,这个里面包括他继承的东西,还有自己的东西;

我们的p1执行这个空间的开始位置,但是能够访问的范围只有base1这么大,p2就不会指向这个空间的开始位置,而是发生偏移,指向这个base2的起始位置,而且可以访问的空间只有base2的范围,但是这个p3指针指向的位置就是这个空间的起始位置,但是这个指针可以访问的范围就会比较大,这个模型他是都可以进行访问的,因为这个指针是一个derive类的;

(3)下面的这个是和虚继承相关的一道题目:

这个里面,我们的BC是共同使用同一份A的,我们这个打印的顺序是按照初始化列表的顺序,D里面的初始化列表里面先是B,但是这个B是A的继承,所以这个打印的结果就是ABCD,最后再去执行这个函数体,打印D;

这个里面A实际上就只会被初始化一次,因为这个A是BC公用的;

当我们把这个题目稍微变换一下,就是把这个虚函数的virtula去掉,这个A要调用几次初始化,这个时候只会进行两次初始化,这个时候的打印结果就是ABABC,因为这个时候已经不是虚继承了,而是普通的继承,B里面有一个A,C里面也有一个A,各自进行相应的初始化,这个时候D里面就不需要进行这个A的初始化了,这个时候A只会进行两次初始化的过程;这个地方不是虚继承,但是这个地方是满足菱形继承的,只不过我们没有使用虚继承的方式去解决这个问题而已;A的初始化要想执行三次,就必须要要3个A对象,但是这个菱形继承里面显然是没有3个A对象的,因此只会进行两次初始化;

我们把这个题目在虚继承的基础上面,把继承的顺序变换一下,就是原来的这个题目是先继承B,再去继承C,但是这个D里面的初始化列表的顺序是不变的,这个时候虽然在初始化列表里面显示B,后是C,但是这个打印的时候是按照声明的顺序打印的,这个里面集继承的顺序就是声明的顺序,即ACBD;

由此可见,这个继承里面的虚继承相对复杂,不建议我们日常去使用;