C++知识第三篇之继承

news/2024/12/17 4:42:10/

C++继承

继承是面向对象编程的重要特征,是对类设计层次的复用

文章目录

  • C++继承
    • 一.介绍
      • 1.继承定义
      • 2.继承方式
      • 3.class与struct
    • 二.作用域
      • 1.成员变量
      • 2.成员函数
    • 三.赋值转换
      • 1.给基类对象赋值
      • 2.给基类对象指针赋值
    • 四.派生类的默认函数
    • 五. 其他
      • 1.友元
      • 2.静态
    • 六.继承
      • 1.单继承
      • 2.多继承
      • 3.菱形继承
      • 4.虚拟继承
    • 七.其他
      • 1.如何定义一个不能被继承的类?
      • 2. 菱形继承中如果合理使用虚拟继承?

一.介绍

1.继承定义

class Parent
{};class Child :public Parent
{};

Parent:是父类,也称作基类(base class);Student:是子类,也称作派生类(derived class)

public:表示继承方式为公共继承

2.继承方式

子类继承父类,有3种继承方式

继承方式的作用是:

对于父类非private的成员,使其在子类中的访问权限为MIN(继承方式,父类成员访问限制符)

如:
在这里插入图片描述
private成员pc在子类中是不可见的,不能在子类中被访问。

  • public继承

    最常用的继承方式,不改变父类成员在子类的访问权限

  • protected继承

    可将父类的public成员,在子类中为protected

  • private继承

    可将父类的public、protected成员,在子类中为private

这个时候可以让我们回顾一下,类中访问限制符的作用

  1. public(公有):其修饰的成员可以在类外直接访问
  2. protected(保护)与private(私有):其修饰的成员不能在类外直接访问

此时就可以发现protected与private的异同了:

如果成员不想在类外被直接访问,则可以用protected或private修饰。但如果需要在子类中被访问,则需要设置为protected。因为private修饰的成员在子类中是不可见(虽然被子类继承了,但是子类不能访问)。

3.class与struct

  • 用class作为类声明的关键字

如果派生类是使用class关键字,则默认继承(不显示表明)方式为private
在这里插入图片描述

  • 用struct作为类声明的关键字

如果派生类是使用struct关键字,则默认继承(不显示表明)方式为private
在这里插入图片描述

无论使用那种,最好显示的写出继承方式

二.作用域

不同类都有其自己的类域,因此基类和派生类都有独立的作用域

例如,上例中的Children类,其继承了Parent类,并继承得到Parent类中的成员,但是这些成员却不在Children的作用域里。

这很好理解,毕竟有两个{},基类成员都在基类的类体中声明(定义)。其次,我们可以在派生类中,声明(定义)与基类同名的成员。(要知道在相同作用域中,定义同名的变量是会引起命名冲突的)


如果派生类和基类中有同名成员,派生类成员将屏蔽对基类同名成员的直接访问,即会优先访问派生类的成员,也称隐藏重定义

此时访问基类成员需要显示指定基类的作用域

1.成员变量

在这里插入图片描述

c.pa:访问的是Children中的成员pa c.Parent::pa:访问到Parent中的成员pa

2.成员函数

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
在这里插入图片描述


在这里插入图片描述

Parent中的show()和Children中的show(),有不同的参数列表,但是并不构成函数重载,因为函数重载的条件是需要在相同作用域中。这两个函数构成隐藏关系

tips:实际中最好不要在继承体系里定义同名成员


三.赋值转换

public继承方式下

派生类的对象可以赋值给基类的对象、基类的指针、基类的引用

这种赋值操作又被叫做切片或者切割,比喻将派生类对象中基类的那部分切给基类进行赋值。

但是基类对象不能赋值给派生类对象

1.给基类对象赋值

在这里插入图片描述

将对象c中Parent部分切片赋值给对象p

2.给基类对象指针赋值

在这里插入图片描述

pp指向对象c中Parent部分的切片

引用:底层也是指针。

四.派生类的默认函数

对于一个空类,经由编译器处理过后,会为它声明一个默认构造函数、一个拷贝构造函数、一个赋值运算符重载、一个析构函数,且这些函数都是public的。称之为默认成员函数。

默认构造函数:

    会先(在初始化列表的位置)调用基类的默认构造函数,完成基类的创建。

构造函数:

    如果基类没有默认构造函数,则需要在初始化列表显示调用基类的构造函数

拷贝构造函数:

    如果基类和派生类的拷贝构造函数都是编译器生成的,会先(在初始化列表的位置)调用基类的拷贝构造函数。

    如果基类显示定义了,则需要在派生类的拷贝构造函数中显示调用,否则不会完成对基类部分的值拷贝(如果不是在参数列表位置调用的,则会先自动调用基类的默认构造函数,如果基类无默认构造,则会报错)

析构函数:

    析构函数基本上都是当对象的生命周期结束后,由编译器自动调用的。在继承体系中,当一个派生类对象需要释放,会先调用派生类的析构函数,再调用其基类的析构函数。

在这里插入图片描述

五. 其他

1.友元

友元的目的是打破封装,使protected或private的类成员也可以被类外访问

友元关系不能被继承
在这里插入图片描述

友元函数show()并非Parent类的成员函数,只是通过friend关键字同类外产生联系

友元类同理

2.静态

对于基类的静态成员,无论其派生类有多少个,都共用着同一个静态成员
在这里插入图片描述

静态函数同理

六.继承

1.单继承

一个派生类只有一个直接基类的关系称为单继承

class Grandparent
{};
class Parent :public Grandparent
{};
class Children :public Parent
{};

Greadparent–>Parent–>Children,单继承

2.多继承

一个派生类有两个或以上直接基类的关系称为多继承

class Father
{};
class Mother
{};
class Children :public Father, public Mother
{};

Children<—Father & Mother,多继承

对基类的初始化顺序,是根据继承的先后顺序
在这里插入图片描述

Children类的默认构造函数中,在其初始列表位置,即使先调用Mother(),后Father(),结果依旧是按继承的顺序先构造Father,后Mother。(初始化列表的错误使用示例,最好按照声明的次序条列)


多继承有可能会出现二义性的问题
在这里插入图片描述

3.菱形继承

菱形继承是多继承的一种特例

class Grandma
{};
class Father :public Grandma
{};
class Mother :public Grandma
{};
class Children :public Father, public Mother
{};

Father是继承于其Grandma,Mother是继承于其Grandma,

当Children继承Father和Mother后,就间接的继承了两个Grandma对象
在这里插入图片描述

二义性问题
在这里插入图片描述

数据冗余问题

对于派生类如果只是需要一份基类的成员即可,那么菱形继承也会造成数据冗余。例如上例的Children类间接的继承了两份Grandma。
在这里插入图片描述

4.虚拟继承

意义:为解决由菱形继承而导致的数据冗余和二义性问题


示例:

class Grandma
{
public:int i;
};
class Father :virtual public Grandma
{};
class Mother :virtual public Grandma
{};
class Children :public Father, public Mother
{};

让Father和Mother,虚拟(virtual)继承Grandma

此时会产生什么变化呢?
在这里插入图片描述

可以看见当Father、Mother虚拟继承后,其首位多出来4个字节的空间,存放着某种数据

在这里插入图片描述

首位置是一个指针,指针内存放着地址,该地址指向一张表。指针是虚基表指针,表是虚基表

虚基表中存放的是偏移量,通过第二个偏移量可以找到基类成员变量的地址

可以看出Father、Mother在虚拟(virtual)继承后,就隐式的增加了一个虚基表指针的成员(属于派生类Father、Mother),且所占空间额外增加了4个字节。


下面来看Children类的变化
在这里插入图片描述

由于Father、Mother都是虚拟继承于Grandma,其各自的虚基表中第二个偏移量都是指向来着于Grandma的成员变量。因此在Children类c中唯有一份Grandma成员变量

解决了数据冗余和二义性问题


在实际中建议避免定义出菱形继承。

七.其他

1.如何定义一个不能被继承的类?

  • 将构造函数私有化

    通过无法实例化来间接使A类无法被继承
    在这里插入图片描述

     因此需要提供一个接口GetA,来返回A类型对象

  • 将析构函数私有化

    通过无法实例化来间接使A类无法被继承
    在这里插入图片描述

  • final

    c++11,不同于前两种,使用final来修饰类后,如果有继承操作就会报错
    在这里插入图片描述

2. 菱形继承中如果合理使用虚拟继承?

在下图的菱形继承中,只对BC类使用虚拟继承,就可以解决数据冗余二义性问题
在这里插入图片描述

如果在菱形继承中,每个继承关系都使用虚拟继承,也可以解决问题,但是没有必要,而且还会使空间增大(虚基表指针),并且性能和复杂度都有问题。

🦀🦀观看~~


http://www.ppmy.cn/news/90639.html

相关文章

教学设计、教案与学案

一、教学设计 &#xff08;一&#xff09;教学设计的定义 什么是教学设计&#xff1f;概括一下国内外不同说法&#xff0c;大体可以明确为&#xff1a;教学设计是一个系统化规划教学系统的过程&#xff0c;是根据教学对象和教学目标&#xff0c;确定合适的教学起点与终点&…

【业务功能篇03】Springboot+POI 带图片的导出Excel

继前面介绍的 Springboot+mybatis-plus+POI实现表单数据导出Excel 这篇实现功能介绍,后期业务又有新的导出需求,就是在导出表单数据的同时,在表单下面位置,放入对应一个业务的图片数据,这些图片数据,就是结合表格的数据,在前端的表格下面的位置展示的,比如针对时间-数量…

三位一体,新华三绿洲3.0数据平台聚焦五大提升

如何有效发挥出数据要素的价值&#xff1f;--这已成为行业用户在数字化转型和智能化升级中的一道必答题。 从2020年《关于构建更加完善的要素市场化配置体制机制的意见》首次明确“数据”成为五大生产要素之一&#xff0c;到去年底《中共中央、国务院关于构建数据基础制度更好…

国内外安全漏洞公共资源库

国内外主要安全漏洞公共资源库,统计如下,渗透代码免费和商业社区也提供了,随时更新统计 国外 美国著名安全公司Offensive Security的漏洞库 Exploit Database - Exploits for Penetration Testers, Researchers, and Ethical Hackers 赛门铁克的漏洞库 http://www.securi…

【分享】科大讯飞星火认知大模型(初体验)

前言&#xff1a; 哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 随着人工智能技术的迅猛发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;成为了热门话题。在众多NLP模型中&#xff0c;科大讯飞星火认知大模型成为了一个备受瞩目的新秀&#xff0c;今天我们来了解…

17种常见VR推广渠道,你知道几个?

随着各方面技术的成熟&#xff0c;VR内容越来越多地出现在了生活的各个角落&#xff0c;凭借其身临其境的3D沉浸式体验&#xff0c;惊艳了不少消费者&#xff0c;为线上平台、实体店铺导流变现实现了极大的价值&#xff0c;成为了当下商企最受欢迎的营销模式。 此前我们经常收…

照片资源异地共享 ? tftgallery、xampp、快解析三种工具就能实现!

我的工作中&#xff0c;经常会收到处理各种图片的任务&#xff0c;在处理完图片之后&#xff0c;怎么发送给客户呢&#xff1f;传输的实现&#xff0c;需要一个安全而稳定的环境和即时方便的工具去进行操作与下载。一般情况下&#xff0c;我们大多会选择微信、QQ来作为传输下载…

ChatGPT和软件测试实践与思考

前言 关于最近大火的ChatGPT相信各位也听过不同渠道听说过他的厉害&#xff0c;目前发展趋势比较火热&#xff0c;科技公司都有在考虑怎么使用ChatGPT进行提高研发效率以及办公效率&#xff0c;最近我所在的公司也有在要求大家使用ChatGPT进行改善工作效率&#xff0c;所以引发…