当前位置: 首页 > news >正文

【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)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类&#xff0c;被继承的称为基类…...

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 分支&#xff1a;master 分支只有一个&#xff0c;名称即为 master。GitHub 现在叫 main develop 分支&#xff1a;develop 分支只有一个&#xff0c;名称即为 developfeature 分支&#xff1a;feature/<功能名>&#xff0c;例如&#xff1a;featu…...

一键Mock工具(Http协议接口调试工具)

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

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开始吧&#xff0c;因为从写文时起&#xff0c;大部分手机都到5.0了。 目录 Android5.0 &#xff08;Lollipop 棒棒糖&#xff09;新特性 Android6.0新特性 Android7.0新特性 Android8.0(O)新特性 Android9.0新特性 Android10.0(Q)新特性 Android11…...

9.0 Android中的网络技术

Android中网络相关的技术&#xff0c;主要分别两种&#xff0c;一种为直接显示网页&#xff0c;另外一种为获取服务器中的数据进行设置。 权限声明 访问网络是需要声明权限 <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"…...

linux查看端口是否被占用 / 包含某个字符的文件/当前正在运行的进程/根据端口号查找进程

查看端口是否被占用 netstat -tuln | grep 80查看包含某个字符的文件 grep -rl "aaa" .r &#xff1a;递归搜索子目录。l &#xff1a;只显示包含匹配字符串的文件名。 ack "your_string"查看当前正在运行的进程 ps aux或者使用 top 命令用于实时显示当…...

解锁 JavaScript ES6:函数与对象的高级扩展功能

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; ES5、ES6介绍 文章目录 &#x1f4af;ES6函数扩展&#x1f353;1 默认参数&#x1f35…...

算法金 | 10 大必知的自动化机器学习库(Python)

本文来源公众号“算法金”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;10 大必知的自动化机器学习库&#xff08;Python&#xff09; 一、入门级自动化机器学习库 1.1 Auto-Sklearn 简介&#xff1a; Auto-Sklearn 是一个自动…...

微信小游戏开发难度大吗?开发流程有哪些?

微信小游戏的开发难度因项目的复杂度和规模而定&#xff0c;一般来说&#xff0c;休闲益智类的小游戏的开发周期相对较短&#xff0c;大约在10个工作日到1个月。如果涉及到复杂的算法、高级的交互或特殊的效果&#xff0c;开发时间可能会相应延长。 微信小游戏的开发流程包括需…...

Qt程序打包成单个exe文件

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

【机器学习】GANs网络在图像和视频技术中的应用前景

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 1. &#x1f525;引言 背景介绍 研究意义 2. &#x1f388;GANs的基本概念和工作原理 生成对抗网络简介 工作原理 3. &#x1f916;GANs在图像生成中的应用 图像超分辨率 工作原理 图像去噪 工作原理 图…...

MFC 使用sapi文字转换为语音

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

(Git)多人协作1

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

MySQL-分组函数

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

【C语言】联合(共用体)

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

【博客715】如何从victorimametrics集群中下线vmstorage节点

How to Decommission a vmstorage Node from a VictoriaMetrics Cluster 我们需要从VictoriaMetrics 集群中优雅地移除一个 vmstorage 节点。每个 vmstorage 节点都包含自己的数据部分&#xff0c;从集群中移除 vmstorage 节点会导致图表出现空白&#xff08;因为复制超出了范…...

Redis缓存技术详解与实战

Redis缓存技术详解与实战 Redis作为一个开源的内存数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息代理。在现代高并发、大数据量处理的系统中&#xff0c;Redis作为缓存层的应用越来越广泛。本文将详细讲解Redis在查询、添加缓存、更新缓存、缓存预热、缓存穿透、…...

业务架构的位置及关系

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

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

轻量级Docker管理工具Docker Switchboard

简介 什么是 Docker Switchboard &#xff1f; Docker Switchboard 是一个轻量级的 Web 应用程序&#xff0c;用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器&#xff0c;使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...