【C++】——继承(详解)
一 继承的定义和概念
1.1 继承的定义
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类,被继承的称为基类
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
下面由代码来进行理解吧
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
protected:string _name = "喜羊羊";int _age = 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}
这段代码分为两个个部分
class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
protected:string _name = "喜羊羊";int _age = 21;
};
这个也就是我们之前写的class类的写法
第二部分
class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};
对于学生和老师来说,他们都有一个共同的特性,那就是人,那我们把人的属性封装起来,然后用老师和学生去复用他,你也就达到了目的,这里的复用也可以称之为继承。
那继承的方法是什么呢?
class 新类的名字:继承方式 继承类的名字{};
就和这个例子一样
class Student : public Person
{
protected:int _stuid; // 学号
};
这里的Student和Tercher我们规范里面称之为派生类,对于Person类来说我们称之为基类。
但是一般来说我们会称之为子类和父类。
1.2 继承的访问权限
由上面我们知道,public是一种继承的方式,但是还有其他的,他们继承方式的不同,访问权限也就不一样
可以看出全部组合起来有很多,但是我们发现一个规律就是他们都是向下取舍的。
我们知道它们三个的权限大小分别为 public>protected>private ,所以如果我们把它们两两组合,取的是权限小的那一个。
1.3 继承的细节
1.如果是private成员,那么无论什么方式都是不可见的,这里不可见是指派生类在类里面和类外面都不能访问,但是它们确确实实是继承了
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
private:string _name = "喜羊羊";int _age = 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};
上面代码中Person成员变量变为私有,但是确确实实是继承了,唯一的就是不能访问
2.对于protected来说,它不像private那样那么严格,它可以让你在类里面访问它,但是在类外面也不能访问。可以看出保护成员限定符是因继承才出现的。
3. 关键字struct默认继承的方式是public,class默认继承方式是private;
二 .基类和派生类对象赋值转换
我们之前的类型转换是创建一个临时变量然后进行赋值,但是这里不一样,这里采用的切片的方式进行的
也就是说子类给父类的时候,会进行一个类似切片的操作,把指给父类,但是如果是父类给子类,那么就会有问题了,因为父类没有子类的_No成员,所以给不了它相应的值。
这里也可以用指针和引用进行操作,但是也有许多情况
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
protected:string _name;string _sex;int _age;
};
class Student : public Person
{
public:int _No; // 学号
};
int main()
{Student sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象//sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobj;Student* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问ps2->_No = 10;return 0;
}
这里着重看一下后面这段
pp = &sobj;Student* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问ps2->_No = 10;
这里的第一部分pp实际上是指向student对象,所以这里ps1也指向student对象,所以可以访问里面的成员
但是第二部分pp是指向Person对象,这里直接强转,相当于告诉编译器忽略这个事实,虽然也是转了,ps2也指向这个对象,但是里面没有_No这个成员,所以会发生越界访问的存在
三 继承中的作用域
1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。
3.1 同名成员变量
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected:string _name = "小李子"; // 姓名int _num = 111;// 身份证号
};
class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号
};
void Test()
{Student s1;s1.Print();
};
void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
我们在打印身份证号的时候采用了指定类域的方式,如果不这样,那么就会导致编译器分不清,那么就直接打印子类的成员了
3.2 同名成员函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};
B中的fun和A中的fun不是构成重载,因为不是在同一作用域
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
四 派生类的默认成员函数
4.1 构造函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}
protected:int _age;
};int main()
{student st("沸羊羊", 18);return 0;
}
对于构造来说,它是先调用父类的构造函数,然后再调用子类的构造函数
子类的构造函数没有去初始化父类的,这里是编译器自己去调用父类的默认构造函数,跟自定义类型是一个道理。
4.2 析构函数
这里析构函数和构造函数相反,这里是先调用子类的,再调用父类的
这里也可以用先构造的后析构,后构造的先析构来解释
class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}
protected:int _age;
};
如果我们在子类中调用父类的析构,如果是指针类型,就会被析构两次。其次之所以先调用子类的析构后调用父类的析构,是因为可能析构的时候存在某些记录工作,所以不能先调用父类的析构函数
4.3 拷贝构造
派生类对象通常包含基类部分和派生类特有的部分。当创建一个派生类对象的副本时,我们需要确保这两部分都被正确地复制。基类部分的复制是通过调用基类的拷贝构造函数来完成的。
如果不调用父类的拷贝构造,那么就会导致资源缺失
class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}Person(const Person&s):_name(s._name){}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}student(const student&s) :Person(s),_age(s._age)//这里Person的拷贝,直接传一个s过去切片就行{}
protected:int _age;
};
这里的拷贝构造重点就在于巧妙运用了Person的切片,其实这里可以不写,编译器默认生成的就够用了,但是我们要显示调用就必须这样写
4.4 赋值运算符重载
class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}Person(const Person&s):_name(s._name){}Person& operator=(const Person&s){if (this != &s){_name = s._name;}return *this;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}student(const student&s) :Person(s),_age(s._age){}student& operator=(const student& s){if (this != &s){Person::operator=(s);_age = s._age;}return *this;}
protected:int _age;
};
这里的赋值运算符重载需要指定类域,不然就会出现死循环,一直调用自己的成员函数,因为这两个是同名的,属于隐藏关系。
五 单继承和多继承
单继承:
一个子类只有一个父类
多继承:
一个子类有多个父类
菱形继承:
菱形继承是多继承的一个特例,同时菱形继承也弄出了很多问题
1.在菱形继承中,Student和Teach都继承了Person里面的成员变量a,后面Assistant继承了它们两个,那么Assistant是不是有两个a呢,这里就是数据的二义性。
2.对于二义性我们可以加访问限定符去解决,但是我们还是存在一个问题就是有两个a,我们继承下来就想要一个,这个就是数据冗余,对于数据冗余我们能用的就是虚继承去解决
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
// class C : public A
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d._a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
如果我们直接访问
d._a = 1;
但是我们加上就欧克了
d.B::_a = 1;
这里也就解决数据二义性的问题,但是对于数据冗余,我们采用虚继承,这里继承以后就是把_a当作成共有的了
那么虚继承就是 会在类B和类C里面生成一个虚基表指针,这指针指向一张表,表里面存有偏移量,然后通过这个偏移量找到_a,所以这里的_a是共有的。
六 继承其他问题
1. 继承如果父类有友元函数,继承以后,友元函数是不能被子类使用的。
2. 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。这里也可以理解为是共有的,所以不会重复。
七 总结
以上就是继承的全部内容了,希望对你有所帮助
相关文章:

【C++】——继承(详解)
一 继承的定义和概念 1.1 继承的定义 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保 持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类,被继承的称为基类…...
openGauss学习笔记-299 openGauss AI特性-AI4DB数据库自治运维-DBMind的AI子功能-SQLdiag慢SQL发现
文章目录 openGauss学习笔记-299 openGauss AI特性-AI4DB数据库自治运维-DBMind的AI子功能-SQLdiag慢SQL发现299.1 概述299.2 使用指导299.2.1 前提条件299.2.2 SQL流水采集方法299.2.3 操作步骤299.2.4 使用方法示例299.3 获取帮助299.4 命令参考299.5 常见问题处理openGauss学…...
Git 分支管理规范化[Git Flow ]分支管理策略
分支命名规范 master 分支:master 分支只有一个,名称即为 master。GitHub 现在叫 main develop 分支:develop 分支只有一个,名称即为 developfeature 分支:feature/<功能名>,例如:featu…...

一键Mock工具(Http协议接口调试工具)
点击下载《一键Mock工具(Http协议接口调试工具》 1. 前言 在进行Web开发时,前端小伙伴通常是和后端开发人员同步进行项目开发,经常会遇到后端开发人员接口还没开发完,也没有可以调试的环境,只能按照接口文档进行“脑…...

Golang的context
目录 context的基本使用 为什么需要context Context interface 标准 error emptyCtx cancelCtx Deadline 方法 Done 方法 Err 方法 Value 方法 context.WithCancel() newCancelCtx WithCancel中propagateCancel cancel timerCtx valueCtx context的基本使用…...
Android 各个版本名称和特性总结(持续更新)
我们就从Android 5.0开始吧,因为从写文时起,大部分手机都到5.0了。 目录 Android5.0 (Lollipop 棒棒糖)新特性 Android6.0新特性 Android7.0新特性 Android8.0(O)新特性 Android9.0新特性 Android10.0(Q)新特性 Android11…...
9.0 Android中的网络技术
Android中网络相关的技术,主要分别两种,一种为直接显示网页,另外一种为获取服务器中的数据进行设置。 权限声明 访问网络是需要声明权限 <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"…...
linux查看端口是否被占用 / 包含某个字符的文件/当前正在运行的进程/根据端口号查找进程
查看端口是否被占用 netstat -tuln | grep 80查看包含某个字符的文件 grep -rl "aaa" .r :递归搜索子目录。l :只显示包含匹配字符串的文件名。 ack "your_string"查看当前正在运行的进程 ps aux或者使用 top 命令用于实时显示当…...

解锁 JavaScript ES6:函数与对象的高级扩展功能
个人主页:学习前端的小z 个人专栏:JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论! ES5、ES6介绍 文章目录 💯ES6函数扩展🍓1 默认参数ἵ…...
算法金 | 10 大必知的自动化机器学习库(Python)
本文来源公众号“算法金”,仅用于学术分享,侵权删,干货满满。 原文链接:10 大必知的自动化机器学习库(Python) 一、入门级自动化机器学习库 1.1 Auto-Sklearn 简介: Auto-Sklearn 是一个自动…...
微信小游戏开发难度大吗?开发流程有哪些?
微信小游戏的开发难度因项目的复杂度和规模而定,一般来说,休闲益智类的小游戏的开发周期相对较短,大约在10个工作日到1个月。如果涉及到复杂的算法、高级的交互或特殊的效果,开发时间可能会相应延长。 微信小游戏的开发流程包括需…...

Qt程序打包成单个exe文件
文章目录 0. 准备工作1. 使用 windeployqt 提取必要的动态链接库和资源文件1.1 操作步骤1.2 补充 2. 使用 Enigma Virtual Box将文件夹打包成单个exe2.1 操作步骤 0. 准备工作 Qt程序打包用到的工具有: windeployqt :安装Qt时自带Enigma Virtual Box 下…...

【机器学习】GANs网络在图像和视频技术中的应用前景
📝个人主页:哈__ 期待您的关注 目录 1. 🔥引言 背景介绍 研究意义 2. 🎈GANs的基本概念和工作原理 生成对抗网络简介 工作原理 3. 🤖GANs在图像生成中的应用 图像超分辨率 工作原理 图像去噪 工作原理 图…...

MFC 使用sapi文字转换为语音
文章目录 添加头文件声明变量 添加头文件 声明变量 pSpVoice NULL; //默认构造函数中初始化为空 bool CChKBarSCCodeApp::InitSpVoice() {HRESULT hr ::CoInitialize(NULL); // COM初始化if (!SUCCEEDED(hr)){AfxMessageBox(_T("声音环境初始化失败!…...

(Git)多人协作1
文章目录 前言总结 前言 目标:master分支下file.txt文件新增“aaa”,“bbb” 实现:开发者1新增“aaa”,开发者2新增“bbb” 条件:在同一个分支下协作完成 实际开发过程中,每个用户都与属于自己的码云账户,如果想要进…...

MySQL-分组函数
041-分组函数 重点:所有的分组函数都是自动忽略NULL的 分组函数的执行原则:先分组,然后对每一组数据执行分组函数。如果没有分组语句group by的话,整张表的数据自成一组。 分组函数包括五个: max:最大值mi…...

【C语言】联合(共用体)
目录 一、什么是联合体 二、联合类型的声明 三、联合变量的创建 四、联合的特点 五、联合体大小的计算 六、联合的应用(判断大小端) 七、联合体的优缺点 7.1 优点 7.2 缺点 一、什么是联合体 联合也是一种特殊的自定义类型。由多个不同类型的数…...

【博客715】如何从victorimametrics集群中下线vmstorage节点
How to Decommission a vmstorage Node from a VictoriaMetrics Cluster 我们需要从VictoriaMetrics 集群中优雅地移除一个 vmstorage 节点。每个 vmstorage 节点都包含自己的数据部分,从集群中移除 vmstorage 节点会导致图表出现空白(因为复制超出了范…...
Redis缓存技术详解与实战
Redis缓存技术详解与实战 Redis作为一个开源的内存数据结构存储系统,它可以用作数据库、缓存和消息代理。在现代高并发、大数据量处理的系统中,Redis作为缓存层的应用越来越广泛。本文将详细讲解Redis在查询、添加缓存、更新缓存、缓存预热、缓存穿透、…...

业务架构的位置及关系
背景 我们已经了解了业务架构的核心元素组成,以及各个扩展元素,同时对各个元素的关系协同也有了一些了解,那么接下来,我们进一步在宏观层面来看业务架构与其他架构的关系。 企业架构 企业架构有多种理解,也有多种叫…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...