C++——继承

继承,在咱们生活中的意思大概就是继承家业的意思(doge),在C++中,它的意义也类似,我们假设一个情景,外卖软件,有骑手,商家,用户,每个账号都封装一个类,类中有电话姓名地址等,但是,我们给每个账户都封装这个类或许有些麻烦,能不能整一套全能版,使各个账户都能去复用这个类呢?这就是继承的意义所在,继承的本质就是复用。

一、继承的基本语法

我们先正常写一个类,这个类名叫派生类(子类),接下来我们选择一个继承方式(一般为public)再选择继承的类(基类/父类),那么基本语法就是,class 派生类:继承方式 基类。然后我们就正常编写派生类的相关函数与成员变量。

二、继承的原则

我们使用继承语法后,子类就继承了父类中的成员函数,也就是子类虽然没有这个成员函数,但仍可以调用父类的成员函数进行使用。但又不是完全的继承,也就是说,继承后子类虽然也继承了父类的成员变量,但当我们进行修改了时候,子类和父类是分开的(即子类的变动不会影响父类变量实例化的结果),在我们进行实例化的时候是各自的,但函数就是同一个了。

三、继承方式的展开

继承方式一共有public、protected、private继承三种方式,恰好对应了我们三种访问限定符,那么不同的继承方式的区别我们用下面一个表格理解即可。

啥叫不可见呢?就是既不能在类外面访问,类里面也无法访问。(不想继承给子类)但并不是真的没有继承,子类也有该变量只是我们无法访问。但我们可以间接地完成访问,即通过调用继承父类的函数实现不可见变量的访问。

总结一下:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

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

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。 4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 好显示的写出继承方式。

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡 使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

在此之前,我们说过protected和private没有区别,但通过上面第二点我们知道,要是想在子类中使用访问父类中的变量就用protected继承(类外仍无法访问),要是不想访问就用private继承。

四、基类和派生类对象赋值转换

假设student是person的子类,那么我们能把子类的变量、指针及引用赋值给父类呢?正常来说是不可以的,毕竟类型都不相同。但这里却是可以的,我们引入一个概念——赋值兼容转换。也就是子类的对象可以赋值给父类的对象、指针、引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。这个地方不是前面提到的类型转换,是一种特殊的语法规则,中间并未产生临时变量。

对于变量的赋值,我们知道子类的成员变量一定是大于等于父类的成员的变量的,所以赋值的过程就把父类以外的变量切割,剩下的依次赋值即可。

对于指针赋值,赋值后就指向了子类中父类的那部分(都有的)。

对于引用赋值,他引用的是在子类中切割的父类成员。

注意:以上全都是在public继承方式下而言的。且父类不能给子类赋值。

五、继承中的作用域

如果子类和父类有一个同名的成员变量,那么在访问的时候会默认访问子类的变量,当然我们也可以调用父类中的,就像之前的调用类中的函数的方式即可 父类::变量名字,函数也是如此。关于其作用域,有以下几点需要注意。

1. 在继承体系中基类和派生类都有独立的作用域。

2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,也就是说如果访问同名变量的话,他会访问子类而不是父类,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

4. 注意在实际中在继承体系里面最好不要定义同名的成员。!!

六、继承中的默认成员函数

子类的构造函数:子类的内置成员一般不处理,自定义成员,调用其自己的构造,父类成员作为一个整体去调用构造。

子类的拷贝构造:与构造相同,子类的内置成员进行值拷贝,一般不需要自己写。如果子类成员涉及深拷贝就需要我们自己实现。

赋值也和上面类似,但析构函数有的需要我们注意一下,父类析构不能显示调用,因为不能保证先子后父(若先父后子析构时会出现问题)。

七、继承与友元

注意:友元关系是不能继承的,也就是说基类的友元不能访问子类私有和保护成员。(你父亲的朋友不是你的朋友),如果想访问那就在子类中再写一次友元。反过来,子类的友元同样是无法访问父类成员的。

八、友元与静态成员

我们在前面说过,子类继承父类的成员变量以后虽然和父类的变量是同名但不是同一个,但如果父类的成员变量是静态成员变量,那么子类继承下来的变量和父类中的是同一个(地址相同)。

即student和person类中的_count是同一个。

九、复杂的菱形继承与菱形虚拟继承

首先我们来看几个概念:

单继承:一个子类只有一个直接父类时的继承关系

多继承:一个子类有两个或两个以上直接父类时的继承关系

菱形继承:多继承的一种特殊情况

单继承我们就不多说了,从直观上来讲,多继承似乎更加便利,使子类能够继承更加全面的信息,(注意:菱形继承并不是关系一定是菱形,比如下面这个情况也是菱形继承)

但菱形继承在这里有个小坑。我们就以上面的关系为例:假设person类中有一个name变量,那么他就会继承给student和teacher,这没什么问题,但他们再向下继承后,assistant类就会有两个name,就会产生数据冗余和二义性的问题。那怎么解决呢?指定类域

但还是会造成数据冗余。这个问题我们引入一个新关键字来解决——virtual(虚继承),只需要在继承方式前添加virtual即可。注意:一定要在产生数据冗余的两个父类都加virtual,而且像复杂的菱形继承关系,谁最先产生这个问题就先进行虚继承(上图的virtual应该加在bc而不是dc)。在实际中,我们可以用多继承,但不提倡用菱形继承。

十、继承与组合

又来个新词——组合,我们把他和继承一起看:

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。说实话这书面解释作者也表示没看明白,咱们用例子说明:我们现在创建两个类(汽车与其构造的类)

此时这两个类的关系就是组合,即我每次创建一个car类的变量里都有一个tire类,但继承是我每次创建一个car变量,也就相当于创建了一个tire的变量。其实在用法上二者没什么差别。但实际中我们肯定要选一个用,在这里我们推荐:优先使用对象组合而不是类继承。

继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很 大的影响。派生类和基类间的依赖关系很强,耦合度高(两个类之间的关联度大)。

对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象 来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复 用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

以上两段用白话解释为:继承的方式下,你父类的所有东西对我子类都是可见的,如果你父类要进行修改,那么我子类就会引发更大工程的修改去适应。但在组合的情况下,我子类对你父类其中的东西访问相比于继承就会有一定限制,也就是说两个类在编写的过程中关联度就会大大降低,这样修改其中一方时,另一方修改的难度也会大大增加。

实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有 些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用 继承,可以用组合,就用组合。

————本文完,看完后不妨看看下面这几个问题来回顾一下这篇文章吧,求点赞。

1. 什么是菱形继承?菱形继承的问题是什么? 2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的 3. 继承和组合的区别?什么时候用继承?什么时候用组合?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值