从零开始的c++之旅——继承
1. 继承
1.继承概念及定义
继承是面向对象编程的三大特点之一,它使得我们可以在原有类特性的基础之上,增加方法
和属性,这样产生的新的类,称为派生类。
继承 呈现了⾯向对象程序设计的层次结构,以前我们接触的函数层次的 复⽤,继承是类设计
层次的复⽤。
例如我们在实现老师的类teacher和学生的类student时,他们都有姓名/地址/ 电话/年龄等成员
变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们 也有⼀些
不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣
的独有成员函数是学习,⽼师的独有成员函数是授课。
class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 学习 void study(){// ...}
protected:string _name = "peter"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 int _stuid; // 学号
};class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 授课 void teaching(){//...}
protected:string _name = "张三"; // 姓名 int _age = 18; // 年龄 string _address; // 地址 string _tel; // 电话 string _title; // 职称
};
我们可以将他们两个类中冗余的部分提取出来,实现一个新的类Person,就可以复用这些成
员,不需要重新定义,省时省力。
class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄
};
class Student : public Person
{
public:// 学习 void study(){cout << Person::_name << endl;}
protected:int _stuid;// 学号string _name = "李四";
};
class Teacher : public Person
{
public:// 授课 void teaching(){//...}
protected:string title; // 职称 string _name = "王五";};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}
1.2 继承定义
1.2.1 格式
如下图所示,Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。
1.2.2 继承方式及其特点
1.如果我们不想让基类的对象被派生类访问,就将基类中的对象定义为private,这样无论是
哪种继承方式,无论是在类内部还是外部都无法访问。
2.如果想让基类的对象继承之后可以在派生类中被访问,但是不能在类外被访问,就定义成
protect,这也是protect和private的区别,所以如果不涉及继承他们两个的作用是一致的
3.有上表格我们可以总结出继承的规律:
基类的私有成员在派⽣类都是不可⻅。
基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),
public > protected > private。
4.继承方式最好显示写出来,即使我们知道class默认private,struct默认public。
5. 当然虽然c++中的继承方式相对复杂,但是我们实际当中基本都是使用public继承。
也不提倡使用protetced/private继承,因为这两者继承下来的成员都只能在派⽣类的类⾥⾯
使⽤,实 际中扩展维护性不强。
1.3 继承类模板
在继承类模板的时候需要注意,当我们的基类是一个类模板的时候,我们使用其中的方法需
要指定类域,不然编译器找不到对应的方法,因为模板的实例化是按需实例化,只有用了对
饮的方法才对去实例化对应的方法,
template<class T>class stack : public std::vector<T>{public:void push(const T& x){ vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};
2. 基类和派生类之间的转换
public继承的 派生类对象 可以赋值给 基类的指针/基类的引用 。我们形象的将其称为切片。
意思就是将派生类中的基类那部分切出来,基类指针只会指向派生类对象中基类有的那部分
对象,下面为示例
class Person
{
protected :string _name; // 姓名 string _sex; // 性别 int _age; // 年龄
};
class Student : public Person
{
public :int _No ; // 学号
};
int main()
{Student sobj ;// 1.派⽣类对象可以赋值给基类的指针/引⽤ Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错 sobj = pobj;return 0;
需要注意的是 Person& rp = sobj 这条语句在执行过程中并没有产生临时对象,而是直接赋值给了rp。
3. 继承中的作用域
3.1 隐藏规则:
1. 在继承体系中基类和派生类都有独自的作用域。
2. 若基类和派生类有同名成员,则派生类成员将会屏蔽基类对同名成员的直接访问,这种现
象叫做隐藏。
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 住哟i在实际继承体系中最好不要定义同名成员。
3.2 注意事项
函数重载和隐藏都要求同名函数,但是函数重载的行为是要求同一作用域,而隐藏的要求是
两个类为派生类和基类中有函数同名
如果我们要在派生类当中调用基类被隐藏的同名函数,就需要指定类域。
4. 派生类的默认成员函数
4个默认成员函数
类有6个默认成员函数,我们主要讨论其中较为重要的4个。
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那部分成员。如果基类没有默认的 构造函数,则必须在派生类构造函数初始化列表显示调用
Student(const char* name, int num ,const string& address): Person(name)//父类成员调用父类的构造函数,没有就在子类初始化列表显示写, _num(num), _address(address){cout << "Student()" << endl;}
2. 派生类的拷贝函数必须调用基类的拷贝构造完成棋类的拷贝初始化。
Student(const Student& s): Person(s)//把基类看作一个整体, _num(s._num){cout << "Student(const Student& s)" << endl;}
3. 派生类的operator=必须调用基类的operator完成基类的复制。需要注意的是派生类的
operator=会隐藏基类的operator等于,使用显示调用基类operrtor=需要指定类域
Student& operator = (const Student& s){if (this != &s){// 构成隐藏,所以需要显⽰调⽤ Person::operator =(s);//将基类对象看作整体_num = s._num;}return *this;}
4. 派生类的析构函数会在被调用后自动取调用基类的析构函数,因为这样才能保证派 ⽣类对象
先清理派⽣类成员再清理基类成员的顺序。因此我们显示写派生类的析构函数时候需要注意
不用显示写基类的析构函数
~Student(){//不用写基类的析构函数}
5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。
6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(后续的多态章节
会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数
不加 virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。
要点总结
不写,编译器默认生成的行为是什么?
默认生成不符合我们需求,自己写,得怎么写?
特点:子类中继承下来的父类成员当做一个整体对象
构造:
默认:子类成员 内置类型(有缺省值就用,没有不确定)和自定义类型(默认构造) + 父类
成员(必须调用父类默认构造)
拷贝构造:
子类成员 内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类
拷贝构造)
赋值重载:
类似拷贝构造
析构:
子类成员 内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
自己实现的话,注意不需要显示调用父类析构,子类析构函数结束后,会自动调用父类
析构
4. 继承和友元
友元关系不能继承,也就是说基类的友元不能访问派生类私有对象和保护成员
class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
int main()
{Person p;Student s;// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员 // 解决⽅案:Display也变成Student 的友元即可 Display(p, s);return 0;
}
、
5. 继承和静态成员
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都 只有⼀个static成员实例。
class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};
int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的 // 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份 cout << &p._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的 // 说明派⽣类和基类共⽤同⼀份静态成员 cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员 cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}
6. 多继承及其菱形继承问题
6.1 继承模型
单继承: 一个派生类只有个一个直接的基类
多继承: ⼀个派⽣类有两个或以上直接基类,多继承对象在内存中的模型 是,先继承的基类
在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
菱形继承: 是多继承的⼀种特殊情况
从上图得出,菱形继承数据冗余和二义性问题,在Assistant的对象中Person成员会有两份。⽀持多继承就 ⼀定会有菱形继承,所以实践中我们也是不建议 设计出菱形继承这样的模型的。
6.1 虚继承
在具有二义性的类前面加上关键字virtual,可以实现虚继承,解决菱形继承的问题,但是对于
计算机来说会造成多余的新能损失
class Person
{
public:string _name; // 姓名 /*int _tel;int _age;string _gender;string _address;*/// ...
};// 使⽤虚继承Person类
class Student : virtual public Person
{
protected:int _num; //学号
};// 使⽤虚继承Person类
class Teacher : virtual public Person
{
protected:int _id; // 职⼯编号
};// 教授助理
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};int main()
{// 使⽤虚继承,可以解决数据冗余和⼆义性 Assistant a;a._name = "peter";return 0;
}
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形
继承就有 菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱
形继承。多继承可 以认为是C++的缺陷之⼀。
我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤
还是底层 都会复杂很多。
虽然菱形继承会造成一些不必要的麻烦,但是一些底层的实现还是需要用上的。但对于小萌
新来说还是只可远观不可亵玩
8. 继承和组合
public继承是 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
组合式一种 has-a 的关系。 假设B组合了A,每个B对象中都有⼀个A对象。
继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩
箱复⽤。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅。继承
⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依 赖
关系很强,耦合度⾼。
对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获
得。对 象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤因为对
象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。组合类之间没有很强的依赖关 系,
耦合度低。优先使⽤对象组合有助于你保持每个类被封装。
优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过
也不太 那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继
承。类之间的 关系既适合⽤继承(is-a)也适合组合(has-a),就⽤组合。
相关文章:

从零开始的c++之旅——继承
1. 继承 1.继承概念及定义 继承是面向对象编程的三大特点之一,它使得我们可以在原有类特性的基础之上,增加方法 和属性,这样产生的新的类,称为派生类。 继承 呈现了⾯向对象程序设计的层次结构,以前我们接触的…...

电路知识的回顾
参考这个blog,快速回顾一些概念。 电路模型和规律 电路的概念 电路是电子学中的一个基本概念,它是由各种元件按照一定的方式连接起来形成的闭合路径,用来传输电流或电信号。在电路中,电流从电源的一端流出,通过导线…...

使用 `Celery` 配合 `RabbitMQ` 作为消息代理,实现异步任务的调度、重试、定时任务以及错误监控等功能
python基础代码、优化、扩展和监控的完整示例。此示例使用 Celery 配合 RabbitMQ 作为消息代理,实现异步任务的调度、重试、定时任务以及错误监控等功能。 项目结构 我们将项目结构组织如下,以便代码逻辑清晰且易于扩展: project/ │ ├──…...

react-router与react-router-dom的区别
写法上的区别: 写法1: import {Swtich, Route, Router, HashHistory, Link} from react-router-dom;写法2: import {Switch, Route, Router} from react-router; import {HashHistory, Link} from react-router-dom;react-router实现了路由的核心功能 react-router-…...

【研究生必看】把选题和文献交给AI,轻松搞定毕业论文!
在学习和研究的过程中,选题和文献录入真的是让人头疼的事情。面对一堆资料,很多时候我们会感到无从下手,甚至有点焦虑。不过,大家别担心!现在有了像“梅子AI论文”这样的工具,可以帮助我们轻松搞定这些问题…...

Android中同步屏障(Sync Barrier)介绍
在 Android 中,“同步屏障”(Sync Barrier)是 MessageQueue 中的一种机制,允许系统临时忽略同步消息,以便优先处理异步消息。这在需要快速响应的任务(如触摸事件和动画更新)中尤为重要。 在 An…...

真·香!深度体验 zCloud 数据库云管平台 -- DBA日常管理篇
点击蓝字 关注我们 zCloud 作为一款业界领先的数据库云管平台,通过云化自治的部署能力、智能巡检和诊断能力、知识即代码的沉淀能力,为DBA的日常管理工作带来了革新式的简化与优化。经过一周的深度体验,今天笔者与您深入探讨 zCloud 在数据库…...

优雅的遍历JSONArray,获取里面的数据
最近看到有个同事在遍历json数组的时候,用for循环写了一层有一层,那么是否有简便的写法呢?当然有了,下面就有用流的行驶,优雅的遍历数组,获取我们想要的数据 public static void main(String[] args) {Str…...

C#:强大而优雅的编程语言
在当今的软件开发领域,C#作为一种广泛应用的编程语言,以其强大的功能、优雅的语法和丰富的生态系统,受到了众多开发者的喜爱。本文将深入探讨 C#的各个方面,展示它的魅力和优势。 一、C#的历史与发展 C#是由微软公司开发的一种面…...

一个由Deno和React驱动的静态网站生成器
大家好,今天给大家分享一个由 Deno React 驱动的静态网站生成器Pagic。 项目介绍 Pagic 是一个由 Deno React 驱动的静态网站生成器。它配置简单,支持将 md/tsx 文件渲染成静态页面,而且还有大量的官方或第三方主题和插件可供扩展。 核心…...

Python pyautogui库:自动化操作的强大工具
在Python的众多强大库中, pyautogui库脱颖而出,成为了实现自动化操作的得力助手。它允许你通过编程控制鼠标和键盘操作,无论是进行自动化测试、创建宏,还是进行一些重复性的任务,pyautogui都能发挥巨大的作用。 一、安…...

【HTML】——VSCode 基本使用入门和常见操作
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 零:HTML开发工具VSCode的使用 1:创建项目 2:创建格式模板&#x…...

从0开始搭建一个生产级SpringBoot2.0.X项目(八)SpringBoot 使用Redis
前言 最近有个想法想整理一个内容比较完整springboot项目初始化Demo。 SpringBoot使用Redis 缓存数据 一、 pom引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>&…...

Ubuntu20.04两种安装及配置中文界面、输入法、换源、共享文件夹实现,及注意事项
虚拟机安装法 1、新建虚拟机,自定义下一步 任意指定路径 提高处理器数量能加快系统响应 完成以后不要运行,添加镜像文件 导入镜像文件,点击浏览 选择后打开->确认->运行虚拟机 出现这种情况就需要检查虚拟机的配置,操作系统…...

后端Java学习:springboot之文件上传(阿里云OSS存储)
一、什么是阿里云存储? 阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。 二、阿里云…...

python通过lunarcalendar库使用农历日期
农历日期库 介绍 lunarcalendar是一个处理农历日期的库 可以简单通过pip安装:pip install lunarcalendar lunarcalendar的github地址 从公历转为农历 from lunarcalendar import Converter, Solarsolar Solar(2024, 11, 1) lunar Converter.Solar2Lunar(sola…...

MySQL高级--范式与反范式
MySQL高级–范式与反范式 1. 背景 首先让我们来简单了解什么是范式与反范式 如下有部门表(包含:雇员、部门、部门领导) EMPLOYEEDEPARTMENTHEADJonesAccountingJonesSmithEngineeringSmithBrownAccountingJonesGreenEngineeringSmith 我们…...

实验05多重循环---7-02 打印矩形图案
打印一个 m 行 n 列的字符 * 组成的矩形图案。 输入格式: 输入在一行中给出空格隔开的两个整数,分别表示行数 m 和列数 n,其中 m 和 n 分别满足 1≤m≤10,1≤n≤20。 输出格式: 由字符 * 组成的 m 行 n 列的矩形图案。 输入样例: 3 5 输出…...

明源地产ERP WFWebService.asmx 反序列化RCE漏洞复现
0x01 产品简介 明源地产ERP是一款专为房地产行业设计的企业资源规划(ERP)系统,系统集成了项目管理、财务管理、客户关系管理、营销管理等多个模块,旨在帮助房地产企业提升运营效率、降低成本和提高客户满意度。它充分考虑了房地产行业的特性和需求,通过整合企业的各个业务…...

学习笔记:黑马程序员JavaWeb开发教程(2024.11.4)
5.8 请求响应-请求-案例 数据保存在emp.xml文件中,解析XML的工具类XMLParserUtils,其中使用了dom4j的接口,因此要在pom.xml文件中引入dom4j的依赖,用于解析XML文件,实体类Emp用于封装服务器解析的数据 前端页面文件…...

开源自托管数据管理工具全面指南
在大数据时代,企业和组织面临着海量的数据挑战。随着应用程序复杂性的提高以及用户需求不断演变,开发团队需要高效地处理大量数据,以便快速做出决策。然而,在众多信息中,如何识别并有效利用那些对决策至关重要的数据呢…...

护工系统|护工陪护软件|护工系统设计
在现代社会,护工系统的开发成为提升医疗服务质量和效率的重要手段。页面设计作为系统开发的关键环节,必须充分考虑到实用性与用户体验。以下是对护工系统开发页面设计功能的详细阐述: 一、用户登录与权限管理 页面设计首先应设置用户登录模块…...

电商领域软件系统实战:基于TiDB的分布式数据库应用
在电商领域,数据的快速增长和复杂性对数据库系统提出了更高要求。TiDB作为一款开源的分布式数据库,以其兼容MySQL协议、水平扩展能力强、高可用性等特性,在电商系统中得到了广泛应用。本文将围绕TiDB在电商领域的应用,详细介绍其搭…...

鸢尾博客项目开源
1.博客介绍 鸢尾博客是一个基于Spring BootVue3 TypeScript ViteJavaFx的客户端和服务器端的博客系统。项目采用前端与后端分离,支持移动端自适应,配有完备的前台和后台管理功能。后端使用Sa-Token进行权限管理,支持动态菜单权限,服务健康…...

Google封号潮来袭!跨境卖家如何解封?
近期,不少小伙伴在苦苦哀嚎:Google账号又又又又被封啦!对于跨境业务在线的小伙伴来说来说,是一个比较严重的问题。但不必过于担心,以下是一些可能的原因和相应的解决方法,耐心看完,也许对你的账号解封有帮助…...

路径规划 | ROS中多个路径规划算法可视化与性能对比分析
目录 0 专栏介绍1 引言2 禁用局部规划器3 路径规划定性对比实验3.1 加载路径规划器和可视化插件3.2 设置起点和终点3.3 选择规划器规划3.4 不同规划器对比3.5 路径保存和加载 4 路径规划定量对比实验4.1 计算规划耗时4.2 计算规划长度4.3 计算拓展节点数4.4 计算路径曲率4.5 计…...

使用 PyCharm 构建 FastAPI 项目:零基础入门 Web API 开发
使用 PyCharm 构建 FastAPI 项目:零基础入门 Web API 开发 本文提供了一份完整的 FastAPI 入门指南,涵盖从环境搭建、依赖安装到创建并运行一个简单的 FastAPI 应用的各个步骤。通过 FastAPI 和 Uvicorn,开发者可以快速构建现代化的 Web API…...

Prim算法与Dijstra算法
注:参考如下文章和视频 不能说毫不相干,简直是一模一样(Prim vs Dijkstra) 普里姆和迪杰斯特拉太像了,他们有什么区别? Prim算法和Dijkstra算法区别 文章目录 总结数组元素的更新两种算法的完整代码 普里姆算法算法步骤算法描…...

水经微图IOS版5.6.1发布,新增图源二维码分享并修订徒步模式功能
随时随地,微图一下! 水经微图(以下称“微图”)IOS版5.6.1发布,本次升级主要新增了图源二维码分享功能,以及修订过往足迹的徒步模式功能。 当前版本 当前版本号为:5.6.1 如果你发现该版本中存…...

复现第三周
1.eval执行 1)打开题目 简单进行代码审计,而题目又为eval函数说明这里eval() 会执行传入的任意代码,可以通过 cmd 作为参数执行任意 PHP 代码,这里相当于用cmd作为参数来执行url头命令 2)在url头输入命令cmdsystem("ls&quo…...