C++继承(二)

派生类的默认成员函数

派生类的构造函数

注意:子类的构造分为两个部分,一个是构造继承而来的,一个是构造自己的。注意:子类不能直接初始化父类的成员,只能如果想要在子类初始化父类的成员,那么就要调用父类的构造函数。也可以不显式的调用父类的构造函数,编译器会帮你调用,但是编译器调用的是父类的默认的构造函数(默认的构造函数分为:1.全缺省构造函数、2.无参构造函数、3.编译器自动生成的一个默认构造函数);如果父类没有默认构造函数,只有传参构造函数,在子类的构造函数实现处需要手动的调用父类的构造函数如上。

理解:其实就是子类对象实例化时,需要传入参数(可能没有),并且想用参数来构造处从父类继承而来的那部分,那么就需要手动的调用父类的构造函数(子类构造函数实现处),通过子类实例化传进来的参数,如果不需要指定继承而来的那一部分,就无需显示调用父类的构造函数,但是这样的前提是父类有默认构造函数,如果没有,想要构造出子类对象,就必须指定继承而来的成员的参数才可以。

知识回顾;编译器给的默认构造函数对于内置类型不做处理,对于自定义内置类型,调用它的构造函数

派生类的拷贝构造

父类的拷贝构造函数:

子类的拷贝构造函数:

注意:这里的子类的拷贝构造有一个关键的点,可以看出上面父类的拷贝构造函数是需要传入一个相同的父类对象才可以的,而子类拷贝构造函数接收的是子类的对象,那么该如何拷贝构造出子类对象的继承部分呢?  其实这利用了前面讲过的基类和派生类的赋值转换知识,子类的拷贝构造函数只需要使用传进来的子类对象来调用父类的拷贝对象即可,因为子类的对像可以赋值给父类的对象,子类对象给父类对象赋值时,会进行切片,然后赋值。

派生类的赋值运算符重载

父类的赋值运算符重载

子类的赋值运算符重载

注意:这里也使用了子类给父类赋值的切片赋值的原理。

注意:这里在子类的赋值运算符函数内部想要调用父类的赋值运算符需要指定作用域即父类的作用域(因为同名的被隐藏了),否则调用到的是自己的,最终陷入无限递归。

派生类的析构函数

父类的析构函数

子类的析构函数

注意:子类的析构函数和父类的析构函数构成隐藏,子类的析构函数和父类的析构函数构成隐藏,因为他们的名字会被编译器统一处理成destructor。(跟多态有关)。

注意(重要):子类的析构函数实现不需要显式的调用父类的析构函数来析构继承而来的部分,会自动调用,因为这样才能保证析构的顺序(先析构子类再析构父类)。

总结:

派生类的四个默认的成员函数的机制是:派生类初始化、析构、拷贝属于父类的那一部分,就交给父亲这个类来实现(调用父类对应的函数来实现),父亲管父亲的,儿子管儿子的。区别:构造、拷贝构造和赋值都是显式的调用父类的函数,而析构自动调用父类的析构函数。

继承与友元

注意:友元关系不能被继承

如上案例:Display函数是父类的友元函数,所以该函数可以直接访问父类对象的私有成员,但是不能访问子类对象的成员。

继承与静态成员

注意:静态关系可以被继承

理解:也就是说父类和该父类的派生类的所有对象共用一份静态成员。

复杂的菱形继承及菱形虚拟继承(面试)

多重继承(菱形继承)导致的问题:

问题解析:所谓的菱形继承就是说现在有一个person类如上,而student类和teacher类继承于person类,然后有一个类是assistant类即继承了student类也继承了teacher类,那么这样复杂的继承关系就是菱形继承。

菱形继承会导致一些问题:数据的二义性和冗余。也就是说如上图可以直到student、teacher都是继承于person,那么他们两个都拥有继承而来的_name成员,而assitant类又继承了他们,也就拥有了两份_name,当想要访问assitant的_name到底是访问到哪一份_name呢?这样就造成了二义性和冗余了。

解决:二义性可以通过指定作用域来访问到具体的哪一份,但是还是没有彻底的解决,依旧存在冗余没有解决。

虚继承:解决方法

虚继承:解决了多重继承的二义性和冗余。

虚继承原理

注意:菱形继承在内存中的关系就是如上显示,B、C继承于A,而D继承于B、C。这样的菱形继承下,D类的对象d中有来自b、c的两份_a成员,造成了冗余和二义性。对象d在内存中实际占用20个字节。

以下展示的是使用虚继承后的:

注意:对于B、C类虚继承A类,然后D类继承B、C类,D类对象d的内存空间使用情况如上,可以看出d的内存空间使用分别是:B类自己的成员_b,以及一个指针;C类自己的成员_c,以及一个指针;d对象自己的成员_d;最后一个是继承于B、C(B、C继承于A)但只有一份的成员_a。  可以看出使用了虚继承后,很好的解决了多重继承下的冗余以及二义性问题(使得相同的成员保存一份即可)。

上面的指针:_d对象存的两个指针分别是指向两个虚基表的指针(一个B类虚继承A,该指针指向从A虚继承而来的成员;另一个是B虚继承A的),而虚基表存着的是偏移量,通过偏移量可以找到从A那里虚继承而来的_a。

从上面的论述可以看出,使用了虚继承来继承某一个基类,可以解决多重继承(菱形继承)下的冗余和二义性问题,即多重继承下只需要保存相同名字的成员变量一份即可。如此看来,虽然在这里的原本存储相同_a两份也只需要8个字节,而使用了虚继承则需要两个指针+一份_a,看起来有点不划算,并且通过指针找到虚基表再得到偏移量算出_a,效率有一定的损耗;但是如果_a是一个非常大的数组,那么这点损耗根本不值一提。

继承的总结和反思

笔试面试题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值