C++ 继承篇

面向对象语言的三大特性:封装,继承和多态

根据目前学到的知识,对于封装的理解,大致有两层:

  1. 将数据和方法封装,不想让外面看到用private/protected修饰,想让外面看到用public修饰
  2. 类型的行为不满足我们的需求,将类型封装,自主规定类型的行为,比如list迭代器,反向迭代器

从现在开始,进入继承的学习

1. 继承的概念和定义


1.1 继承的概念

有这样的场景,你要完成一个学生管理系统,必然需要描述很多的对象,于是构建很多类,每个类代表不同的群体,比如学生类、老师类、宿管类…定义出来后,发现每个类中都有某些属性是相同的,比如大家都有名字、年龄、性别这样的属性,在每个类中都定义了一遍,显然代码冗余,于是就有了继承

将每个类的公共属性提取出来,单独作为一个类,称为父类/基类;每个群体中持有它们独有的属性,叫做子类/派生类,通过继承的方式,将父类继承给子类,这样子类就有父类的属性和自身独有的属性

在这里插入图片描述

继承是面向对象语言中代码复用的一种重要手段,它允许我们保持原有类的特性,增加新的功能,这样产生的类叫做派生类

1.2 继承的定义

在这里插入图片描述

三种的继承方式+访问限定符决定了在派生类中访问基类成员的方式

规律十分简单,如果是基类的private成员,那么不管何种继承方式,都不能在派生类中直接使用

其他情况,按照public > protected > private的顺序,基态成员在派生类中的修饰方式,按照继承方式和基态类成员修饰符当中最小值

类成员/继承方式public继承protected继承private继承
基类public成员派生类的public成员派生类的protected成员派生类的private成员
基类protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类private成员派生类中不可见派生类中不可见派生类中不可见

在实践中,一般不会对基类成员进行private修饰和private继承

struct的默认继承方式和限定符都是共有;class的默认继承方式和限定符都是私有

2. 基类和派生类赋值转换


C语言中,相关类型之间可以发生隐式类型转换,中间会产生临时变量,C++中延续了这种语法

不相关类型间不能隐式类型转换,但对于基类和派生类,可以发生赋值转换,中间不会产生临时变量,它由编译器特殊处理

对于public继承,每一个派生类对象都是一个特殊的基类对象,这种赋值转换也叫做切割/切片

在这里插入图片描述

  • 派生类对象可以赋值给基类对象/引用/指针
  • 基类对象不能赋值给派生类对象
int main()
{Student s;Person p = s;Person* ptr = &s;Person& ref = s;// 没有产生临时变量,因此可以不加constptr->_name += 'x';ref._age = 1;return 0;
}

在这里插入图片描述

3. 继承中的作用域


  • 继承体系中,基类和派生类有自身独立的作用域
  • 如果基类和派生类有同名成员变量,派生类中默认访问的是自身的,可以通过显示调用访问基类的;该同名变量构成隐藏,也叫重定义
  • 如果是成员函数的隐藏,只要函数名相同就构成隐藏
class Person
{
protected:string _name;int _num = 111;
};class Student : public Person
{
public:void func(){cout << _num << endl;// 默认是自身的成员变量cout << Person::_num << endl;// 指定父类中的成员变量}protected:int _num = 222;
};int main()
{Student s;s.func();return 0;
}
class Person
{
public:void func(int i = 1){cout << "fun(int i)" << endl;}protected:string _name;int _num = 111;
};class Student : public Person
{
public:void func(){Person::func(10);cout << "fun()" << endl;}protected:int _num = 222;
};int main()
{Student s;s.func();// 调用子类中的func()s.Person::func();// 在子类中调用父类中的func()Person p;p.func();// 调用父类中的func()return 0;
}

4. 派生类的默认成员函数


派生类的成员变量分为两部分:

  1. 父类的成员(看作一个整体)
  2. 自身的内置类型和自定义类型按照跟以前一样的方式
  • 默认构造函数会调用父类的默认构造函数初始化父类的成员,如果父类没有默认构造函数,则必须在初始化列表中显示调用

    class Person
    {
    public:Person(const char* name):_name(name){cout << "Person()" << endl;}protected:string _name;
    };class Student : public Person
    {
    public:Student(const char* name, int num):Person(name),_num(num){}protected:int _num;
    };int main()
    {Student s("zhangsan", 4);return 0;
    }
    
  • 拷贝构造调用父类的拷贝构造完成父类成员的拷贝初始化

    class Person
    {
    public:Person(const char* name):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}protected:string _name;
    };class Student : public Person
    {
    public:Student(const char* name, int num):Person(name),_num(num){cout << "Student()" << endl;}// s1(s2);Student(const Student& s):Person(s),_num(s._num){cout << "Student(const Student& s)" << endl;}protected:int _num;
    };int main()
    {Student s1("zhangsan", 4);Student s2(s1);return 0;
    }
    
  • 赋值重载调用父类的赋值重载完成父类成员的初始化

    class Person
    {
    public:Person(const char* name):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){if (this != &p){_name = p._name;cout << "Person& operator=(const Person& p)" << endl;}return *this;}protected:string _name;
    };class Student : public Person
    {
    public:Student(const char* name = "xxxx", int num = 3):Person(name),_num(num){cout << "Student()" << endl;}// s1(s2);Student(const Student& s):Person(s),_num(s._num){cout << "Student(const Student& s)" << endl;}//s1 = s2Student& operator=(const Student& s){if (this != &s){Person::operator=(s);_num = s._num;cout << "Student& operator=(const Student& s)" << endl;}return *this;}protected:int _num;
    };int main()
    {Student s1("zhangsan", 4);Student s2;s2 = s1;return 0;
    }
    
  • 析构函数调用时,会先析构子类的成员,再析构父类的成员;这是为了防止在子类的析构中访问父类的成员,如果先析构父类,就会造成访问非法空间的问题;因此,编译器在构造时,先构造父类,再构造子类;再析构时,会保证先析构子类,再析构父类

    class Person
    {
    public:Person(const char* name):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){if (this != &p){_name = p._name;cout << "Person& operator=(const Person& p)" << endl;}return *this;}~Person(){cout << "~Person()" << endl;}protected:string _name;
    };class Student : public Person
    {
    public:Student(const char* name = "xxxx", int num = 3):Person(name),_num(num){cout << "Student()" << endl;}// s1(s2);Student(const Student& s):Person(s),_num(s._num){cout << "Student(const Student& s)" << endl;}//s1 = s2Student& operator=(const Student& s){if (this != &s){Person::operator=(s);_num = s._num;cout << "Student& operator=(const Student& s)" << endl;}return *this;}~Student(){Person::~Person();cout << "~Student()" << endl;}protected:int _num;
    };int main()
    {Student s1;return 0;
    }
    

5. 继承与友元


基类的友元函数不是派生类的友元函数,也就是说友元不能继承,基类友元函数不能访问派生类的私有和保护成员

class B;
class A
{
public:friend void func(const A& a, const B& b);protected:int _a;
};class B : public A
{
protected:int _b;
};void func(const A& a, const B& b)
{cout << a._a << endl;cout << b._b << endl;// 编译器报错
}int main()
{A a;B b;func(a, b);return 0;
}

6. 继承与静态成员


基类中的静态成员,不管该基类派生出多少个类,静态成员只有一份,所有派生类使用的都是同一个静态成员

可以根据这个特性,计算基类及其派生类一共创建的个数

class Person
{
public:static int _count;Person(){_count++;}protected:string _name;
};int Person::_count = 0;class Student : public Person
{
protected:int _stdid;
};class Other : public Student
{
protected:int _num;
};int main()
{Person p;Student s;Other o;cout << p._count << endl;// 3cout << s._count << endl;// 3cout << o._count << endl;// 3return 0;
}

7.棱形继承


单继承:一个子类只有一个直接父类时称这种继承关系为单继承

在这里插入图片描述

多继承:一个子类有两个及以上的直接父类时称这种继承关系为多继承

在这里插入图片描述

多继承一般用于一个对象同时是两种类别,比如西红柿,它既是水果,又是蔬菜,继承两个父类是很合理的

但是,有多继承就意味着会出现棱形继承

在这里插入图片描述

棱形继承会导致派生类包含了Other包含了两份Person,产生数据冗余和二义性的问题

在这里插入图片描述

class Person
{
protected:string _name;
};class Student : public Person
{
protected:int _stdid;
};class Teacher : public Person
{
protected:int _jobid;
};class Other : public Student, public Teacher
{
public:void func(){cout << _name << endl;// 编译器报错,不知道访问的是Student还是Teacher中的_namecout << _other << endl;}protected:int _other;
};int main()
{Other o;o.func();return 0;
}

C++早期设计时,认为多继承很合理,但在后续使用中就出现了棱形继承的问题,该如何解决呢?

使用虚拟继承,让基类的第一级的派生类继承时加上virtual关键字,表示虚拟继承

class Person
{
public:int _name;
};class Student : virtual public Person
{
public:int _stdid;
};class Teacher : virtual public Person
{
public:int _jobid;
};class Other : public Student, public Teacher
{
public:int _other;
};int main()
{Other o;o.Student::_name = 6;o.Teacher::_name = 7;o._stdid = 1;o._jobid = 2;o._other = 3;return 0;
}

在这里插入图片描述

使用棱形虚拟继承,在内存中,基类被放到了最下面,变成公共的,同时第一级的派生类中多了指针,该指针指向一个数,表示该类到基类的偏移量

棱形虚拟继承中,基类被叫做虚基类,派生类中的指针叫做虚基表指针,指向一个虚基表,里面存放着基类的偏移量

发生切割/切片时,会有指针偏移,指针指向自身的对象

8.继承总结


关于多继承的面试题:

  1. C++有多继承,为什么java没有?

    C++比java先设计,在当时,多继承看起来十分合理,于是就设计了出来,但是没想到出现了很多问题,导致解决非常麻烦,而且生活中也很少使用;而java吸收了这个教训,在设计时就舍弃了多继承

  2. 多继承的问题是什么?

    多继承本身没有任何问题,但有多继承就可能会写出棱形继承

  3. 棱形继承的问题?如何解决?

    数据冗余,二义性;对第一级的派生类使用虚拟继承

  4. 底层角度是如何解决数据冗余和二义性的?

    将基类放到第一级派生类的后面,派生类中加入虚函数指针,指向虚函数表,存放基类的偏移量

继承和组合:

public继承是一种is-a的关系,比如学生和人,学生是人

组合是一种has-a的关系,比如汽车和轮胎,汽车有轮胎

如果使用继承,基类对象对于派生类是可见的,一定程度上破坏了基类的封装,导致基类和派生类耦合度高

而对于组合,自定义对象成员在其他对象中不可见,类和类之间耦合度低

开发软件时,尽量做到类和类之间低耦合,高内聚,因此如果一个对象既能使用继承描述,又能使用组合组合描述,优先使用组合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/8876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[嵌入式系统-71]:RT-Thread-组件:日志管理系统ulog,让运行过程可追溯

目录 ulog 日志 1. ulog 简介 ulog 架构 配置选项 日志级别 日志标签 2. 日志初始化 初始化 去初始化 3. 日志输出 API 4. 日志使用示例 使用示例 在中断 ISR 中使用 同步模式&#xff08;Synchronous Mode&#xff09; 异步模式&#xff08;Asynchronous Mode&…

蓝桥杯EDA客观题

目录 前言 一、PCB类知识点和题目分析 1.电阻 2.电容 3.封装类 4.单位转换类 5.电路板结构类 6.PCB绘制规则 7.立创软件 8.PCB硬件 线性电源和开关电源 二、数电知识点和题目分析 1.门电路 2.逻辑代数 3.组合逻辑电路 4.触发器 5.时序逻辑电路 6.其他 三、模…

vue3+ts之el-tooltip换行显示内容

<el-tooltip placement"top-end"><div slot"content" class"tips"><el-button type"primary" click"exportData">导出</el-button></div><template #content><span class"cont…

【项目实战】使用Yolov8 + tesseract 实现身份证信息解析(OCR) + 输入可为图片或者pdf + 完整代码 + 整体方案 + 全网首发

本项目可用于实验,毕业设计参考等。整体效果如下所示: 说明:图片来源于网络,如有侵权,请联系作者删除。 目录 一 数据集制作

C语言--带环链表问题

继续学习 一、判断链表是否带环 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;用快慢指针&#xff0c;快指针走两步&#xff0c;慢指针走一步&#xff0c;当慢指针走一半快指针进到环里 当慢指针进环&#xff0c;快指针已经在环中转了一会儿了 | |…

关于Java selenium使用前浏览器驱动的下载和环境变量的配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

vue+ant-design+formBuiler表单构建器——技能提升——form design——亲测有效

最近看到后端同事在弄一个后台管理系统&#xff0c;额&#xff0c;前端真的是夹缝中生存啊&#xff0c;AI抢饭碗&#xff0c;后端也想干前端的活儿。。。 他用到了表单构建器&#xff0c;具体效果如下: 网上有很多适用于ElementUi和ant-design的form design插件&#xff0c;下…

武汉星起航:精准市场定位引领跨境电商新潮流,创造辉煌业绩

在跨境电商领域&#xff0c;市场定位的准确性直接关系到企业的成败。武汉星起航电子商务有限公司&#xff0c;凭借其自运营团队的深厚经验和精准洞察力&#xff0c;成功在亚马逊平台开设多家自营店铺&#xff0c;并取得了显著成绩。这一成绩的取得&#xff0c;离不开公司对市场…

设计模式——行为型模式——策略模式(含实际业务使用示例、可拷贝直接运行)

目录 策略模式 定义 组成和UML图 代码示例 实际业务场景下策略模式的使用 策略模式优缺点 使用场景 JDK中使用策略模式示例 参考文档 策略模式 定义 策略模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化…

FMEA助力医疗设备研发制造:领跑未来,实现弯道超车!

医疗设备作为保障人类健康的重要工具&#xff0c;其研发与制造水平直接关系到医疗技术的进步。然而&#xff0c;在激烈的市场竞争中&#xff0c;如何能够让自家医疗设备研发制造实现弯道超车&#xff0c;成为行业佼佼者&#xff1f;答案就在于——FMEA&#xff08;失效模式与影…

试用NXP官方的UDS bootloader

文章目录 1.前言2.资料获取2.1 MCU例程 2.2 开发环境2.3 上位机2.4 硬件 3.工程修改3.1 boot工程修改 3.2 app工程修改4.测试情况5.例程分享 1.前言 最近很多客户在开发S32K系列MCU时咨询是否可以提供基于UDS协议的bootloader。本文以S32K144为例&#xff0c;介绍如何使用NXP官…

【Mybatis操作数据库】入门(一)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MyBatis框架】 本专栏旨在分享MyBatis框架的学习笔记&#xff0c;如有错误定当洗耳恭听&#xff0c;欢迎大家在评论区交流讨论&#x1f…

NGINX App Protect现已支持NGINX开源版 全方位加强现代应用安全防护

近日&#xff0c;F5 NGINX 发布全新升级的NGINX App Protect 5.0版本&#xff0c;将先前专属于NGINX 商业版本NGINX Plus 的现代应用安全能力拓展至NGINX开源版中&#xff0c;为增强现代应用和API安全防护提供全方位支持。此次升级后&#xff0c;适用于云端及本地部署的NGINX A…

软考中级之数据库系统工程师笔记总结(六)多媒体基础

作者&#xff1a;Maynor 博客之星大数据领域Top1,GitHub项目awesome-chatgpt-project作者, 大厂程序员, 全网技术矩阵粉丝7w 公众号&#xff1a;Maynor996&#x1f4e2;博客主页&#xff1a;https://manor.blog.csdn.net &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &am…

基于TL431的线性可调恒压恒流电源的Multisim电路仿真设计

1、线性电源的工作原理 在我们日常应用里&#xff0c;直流电是从市电或电网中的交流电获取的。例如15V直流电压源、24V直流电压源等等。交流电变为直流电的过程大概分为一下几步&#xff1a; 首先&#xff0c;交流电通过变压器降低其电压幅值。接着&#xff0c;经过整流电路进…

韩顺平0基础学Java——第6天

p87-p109 运算符&#xff08;第四章&#xff09; 四种进制 二进制用0b或0B开头 十进制略 八进制用0开头 十六进制0x或0X开头&#xff0c;其中的A—F不区分大小写 10转2&#xff1a;将这个数不断除以2&#xff0c;直到商为0&#xff0c;然后把每步得到的余数倒过来&#…

储能液冷系统中的管路介绍

储能液冷系统中管路占比约10%。储能液冷管路通过向系统中导入冷却剂&#xff0c;使产热元件与冷却剂进行换热&#xff0c;有效提高能源转化效率&#xff0c;并维持系统的温度稳定。 近年来液冷储能市场的兴起&#xff0c;也为尼龙管路打开了新的市场。储能液冷循环管路按照作用…

【GaussTech速递】数据库技术解读之细粒度资源管控

背景 对数据库集群内资源管控与资源隔离一直是企业客户长久以来的诉求。华为云GaussDB作为一款企业级分布式数据库&#xff0c;一直致力于满足企业对大型数据库集群的管理需要。 数据库可以管理的资源有计算资源与存储资源&#xff0c;计算资源包括CPU、内存、IO与网络&#…

【AI大模型】AI大模型热门关键词解析与核心概念入门

&#x1f680; 作者 &#xff1a;“大数据小禅” &#x1f680; 文章简介 &#xff1a;本专栏后续将持续更新大模型相关文章&#xff0c;从开发到微调到应用&#xff0c;需要下载好的模型包可私。 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 目…

【大模型赋能开发者】海云安入选数世咨询LLM驱动数字安全2024——AI安全系列报告

近日&#xff0c;国内知名数字产业领域第三方调研咨询机构数世咨询发布了LLM驱动数字安全2024——AI安全系列报告。报告通过调研、公开信息收集等方式对目前十余家已具备LLM相关的应用能力安全厂商对比分析出了这一领域当前的产业现状并进行了各厂商的能力展示。 海云安凭借近…