1.概念
使用情景:当我们需要新定义的某一个类和现存的类有许多共同点时,我们可以从现存类中继承这些共同变量或者方法到新的类中。后出现的这个类就叫子类/派生类,先出现的类叫父类/基类。
eg:父类(person类),子类(student类),因为学生属于人,所以我们的student可以继承person
2.定义
2.1定义格式
继承普通类
继承格式:
class 派生类名 : 继承限定符 基类名
继承类模板
继承的格式没特别大变化,只要加上尖括号和模板即可
同时我们也可以在继承类模板的同时扩展模板参数的个数
2.2访问方式
(1)我们在类中有三个访问限定符:
1.public
2.protected
3.private
没学继承前,我们认为private和protected的限定等级是一样的,public类内外都可以访问,protected和private只有类内可以访问。
在引入继承知识后,protected和private的区别就体现出来了
基类的protected修饰变量与方法可以给派生类访问,不能给除自身和派生类外的区域访问。
基类的private修饰变量与方法只能由基类自身访问
(2)同时继承也有限定符,和访问限定符一样,只不过是在继承的时候修饰的
总结:派生类类对基类方法或变量的最终访问权限为min(类中访问限定符,继承限定符).也就是以最小的权限为准
3.基类和派生类的转换
(1)public继承的派生类对象可以传给基类的指针或者引用,这是派生类的切片现象导致的。
具体来说就是因为派生类的变量与方法分为两部分,一部分是基类的,一部分是派生类自己的。当基类的指针指向派生类时,会指向属于基类的一部分,引用也是引用属于基类的一部分,用示意图来看就像切片一样
这里的p表示基类的指针或者引用,他只会指向派生类中属于基类的一部分
注意:这里看似是类型转换,但其实不涉及临时对象的生成,而是直接指向该对象
(2)基类对象不能赋值给派生类对象
因为派生类对象具有的方法和变量比基类更多,基类不能在传的时候补上那些变量或方法
(3)基类的指针或引用可以通过强制类型转换赋值给派生类对象的指针或者引用
不过只有基类的指针或者引用是指向派生类对象的时候可以强制类型转换
4.继承中的作用域
(1)基类和派生类都有独立的类域
(2)当派生类中出现了与基类同名的变量,此时基类中的同名变量不能被直接访问,这种现象叫做隐藏
(3)函数的隐藏条件只有一个:函数名相同
接下来我们展示变量的隐藏现象:
这里我们父类的m_n缺省值为1,基类的m_n为2,我们在子类中直接访问m_n发现输出的是子类的缺省值2.说明父类的m_n被隐藏了
隐藏的变量的访问方法:基类 ::
我们使用父类加域作用符就可以指定变量的访问类域为父类域,这样子访问的就是被隐藏的父类同名变量
函数隐藏展示:
我们看到子类的print函数的参数与返回值都与父类不同,只有函数名同,但是仍然构成了函数的隐藏,说明函数隐藏只需要满足函数名相同即可。
隐藏的函数的访问方法:基类 ::
我们在main函数中调用print函数的时候先用父类名加域作用符指定域即可解除隐藏
注意:隐藏和函数重载有个很大的不同之处,隐藏是发生在不同的域中,而函数重载发生在同一个域中
5.派生类的构造
我们把基类当成派生类中的一个整体自定义对象看待
(1)我们实现派生类默认构造的前提是实现了基类的默认构造,因为基类的变量初始化只能由基类的构造完成
这里我们就把father基类所有成员看成整体,当成派生类中一个自定义类型成员,然后传递基类需要的值
(2)派生类的拷贝构造函数也需要调用基类的拷贝构造函数
我们派生类中本来是需要给基类传基类对象的,但是由于派生类不可能有基类对象,我们用派生类对象代替,由于派生类对象传递给基类引用具有切片效应,所以不会报错
(3)赋值重载也是同理,派生类使用赋值重载也要调用基类的
由于operator=构成隐藏,所以派生类中调用基类的operator=需要指定类域
(4)由于多态中有些场景需要对析构函数进行重写,而多态满足的条件中包含函数名相同,所以编译器会对析构函数的名字进行处理,把他们都改为destructor()
(5)析构的调用,派生类调用完析构后会自动调用基类的析构
若有成员需要显示释放,就显示写析构,不过要注意因为析构函数都被编译器处理为同名函数,构成隐藏,所以要指定类域调用父类析构。不过因为必须满足派生类先析构,所以我们自己调用可能会出问题,编译器就不允许我们自己调用父类析构函数,而是在我们调用完子类的析构函数之后自动调用父类析构
6.实现一个不可被继承的类
方法一:将该类的构造私有化
这样子可以让子类的对象无法被创建出来,因为子类对象要被创建需要调用父类构造,而父类的构造被私有化,子类不可见,从而达到该类无法被继承的目的
方法二:使用关键字final修饰
final修饰的类叫最终类,最终类无法被继承
6.1友元关系不可被继承
基类的友元关系是不能被继承到子类中的,如果需要让某个函数可以访问子类成员,则让该函数成为子类的友元函数即可
6.2静态成员与继承
父类中静态成员也是全局的,只是被类域限制,子类继承了静态成员,但是整个继承体系始终只有唯一一个静态成员,而不会因为子类的继承而创建一个新的静态成员。父类的其他成员则不同,子类会创建一个新的
7.多继承与菱形继承
单继承:一个子类只能直接继承一个父类
多继承(不建议使用):一个子类可以同时继承多个父类
菱形继承(不建议使用):两个子类s1,s2先继承自同一个父类f1,然后这两个子类又同时直接给他们的同一个子类s3提供继承,这样子s3就就会拥有两份f1的成员,且这两份成员内容也不一定相同,所以导致了数据冗余与二义性
解决二义性:指定类域来访问数据
解决数据冗余:使用虚继承
7.1虚继承
使用方法:
这样子我们父类的m_n就只有一份了,子类中不再创建父类的变量。从而数据冗余和二义性就解决了
加virtual的地方是多个子类拥有同一个直接父类的这几个子类中
8.继承和组合
继承:“A是一个特殊的B”,派生类是一个特殊的基类,就像用继承vector的方式实现栈
组合:"A中有一个B",A组合了B,那么每个A对象都有一个B对象,就像用容器适配器实现栈一样
特性:
继承属于白箱模式(使用的东西的底层可访问),基类的变化对于派生类的影响很大,耦合度高
组合属于黑箱模式(只可以使用接口),有利于保证封装,耦合度低
使用逻辑:
有组合关系组合优先用组合,实在没有组合关系再用继承