网站 多服务器/网络营销推广平台
一 继承的定义和概念
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在查询、添加缓存、更新缓存、缓存预热、缓存穿透、…...

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

CMS与AI的融合:构建万能表单小程序系统
引言: 随着人工智能技术的飞速发展,MyCMS作为一款功能强大的内容管理系统,通过集成AI技术,进一步拓展了其应用范围和智能化水平。本文将探讨如何利用MyCMS结合AI技术,构建一个能够将用户提交的万能表单数据转化为智能提…...

机器学习常见知识点 2:决策树
文章目录 决策树算法1、决策树树状图2、选择最优决策条件3、决策树算法过程→白话决策树原理决策树构建的基本步骤常见的决策树算法决策树的优缺点 【五分钟机器学习】可视化的决策过程:决策树 Decision Tree 关键词记忆: 纯度、选择最优特征分裂、熵、基…...

海洋CMS admin_notify.php 远程代码执行漏洞复现(CVE-2024-30565)
0x01 产品简介 海洋CMS是一套专为不同需求的站长而设计的内容管理系统,灵活、方便、人性化设计、简单易用是最大的特色,可快速建立一个海量内容的专业网站。海洋CMS基于PHP+MySql技术开发,完全开源免费 、无任何加密代码。 0x02 漏洞概述 海洋CMS admin_notify.php 接口处…...

Spring、Spring MVC、MyBatis和Spring Boot对比
在对比Spring、Spring MVC、MyBatis和Spring Boot时,我们可以从以下几个方面进行详细的分析: Spring框架: 作用:Spring是一个轻量级的IoC(控制反转)和AOP(面向切面编程)容器&#…...

【Linux高级IO】select、poll、epoll
【Linux高级IO】select、poll、epoll toc 作者:爱写代码的刚子 时间:2024.6.5 前言:本篇博客将会介绍面试重点考察的select、poll、epoll IO: input && Output read && write 应用层read&&write的时候,…...

Etcd Raft架构设计和源码剖析2:数据流
Etcd Raft架构设计和源码剖析2:数据流 | Go语言充电站 前言 之前看到一幅描述etcd raft的流程图,感觉非常直观,但和自己看源码的又有些不同,所以自己模仿着画了一下,再介绍一下。 下图从左到右依次分为4个部分&…...

深入理解Qt多线程编程(QThreadPool)
多线程编程在现代软件开发中变得越来越重要,它能够提高应用程序的响应速度和处理性能。在Qt框架中,QThreadPool作为线程池管理工具,被频繁的使用。 目录 概述 接口介绍 底层原理解析 使用方法 概述 QThreadPool是Qt提供的一个线程池实现&a…...

Prisma数据库ORM框架学习
初始化项目 中文网站 点击快速开始,点击创建sql项目,后面一步一步往后走 这个博主也挺全的,推荐下 可以看这个页面初始化项目跟我下面是一样的,这里用得是ts,我下面是js,不需要额外的配置了 1.vscode打开一个空文件夹 2.npm init -y 初始化package.json 3.安装相关依赖 …...

Flutter-使用MethodChannel 实现与iOS交互
前言 使用 MethodChannel 在 Flutter 与原生 Android 和 iOS 之间进行通信,可以让你在 Flutter 应用中调用设备的原生功能。 基础概念 MethodChannel:Flutter 提供的通信机制,允许消息以方法调用的形式在 Flutter 与原生代码之间传递。方法…...

【星海随笔】云解决方案学习日志篇(一) ELK,kibana,Logstash安装
心路历程 本来想最近再研究研究DPDK的。但是自己做一个东西很多时候没有回报。因为自己的低学历问题,类似工作的面试都没有。所以很多东西学了很快就忘了,没有地方可以用。 今天看到了一个大佬,除了发型外,很多想法还是很共鸣的。 Shay Banon 决定开始跟…...