【继承】—— 我与C++的不解之缘(十九)

前言:

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

本篇博客来学习C++中的继承,加油!

一、什么是继承?

​ 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤,继承是类设计层次的复⽤。

简单来说,对于两个类(甚至多个),它们直接有一些相同的成员;这样设计是有些冗余的。

比如下面这连个类,学生和老师,它们之间有很多相同的成员变量(函数):

class Student
{
public:void studying() {}  //学习void identity() {}  //进校园
private:string _name; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话int _id; //学号
};
class Teacher
{
public:void teaching() {}  //教学void identity() {}  //进校园
private:string _name; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话string _title; // 职称
};

​ 对于这种情况,我们就可以将这些公共成员放到一个类Person 中,让StudentTeacher 去继承Person 类的这些公共成员。

class Person
{void identity() {} //进学校protected:string _name; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话
};class Student :public Person
{
public:void study() {};
private:string _id; //学号
};class Teacher : public Person
{void teaching() {} //教学
private:string _title; //职称
};

这样就形成了下图这样的继承关系

在这里插入图片描述

二、继承的定义

​ 我们看到Person是基类,也称作父类。Student是派⽣类,也称作子类。(既叫基类/派生类,也叫父类/子类)

1、定义格式

在这里插入图片描述
在这里插入图片描述

2、继承基类成员访问方式

​ 在看基类成员访问方式之前,先来看一个在【C++类和对象(上)】—— 我与C++的不解之缘(三)-CSDN博客 提到的访问限定符,当初没有去了解它是啥,现在来看一下

在这里插入图片描述

//基类
class A
{
public:int _i1 = 1;
protected:int _i2 = 2;
private:int _i3 = 3;
};
// public 继承
//派生类
class B : public A
{
public:void func1(){//基类中的public成员,派生类中可以直接访问,在类外也可以直接访问cout << _i1 << endl;}void func2(){//基类中的protected成员,派生类中可以直接访问,在类外不能直接访问cout << _i2 << endl;}//void func3()//{//	//基类中的private成员,派生类中不可见, 派生类中和类外都不能访问//	cout << _i3 << endl;//}
};
void test1()
{B bb;//基类的public成员, 在类外可以直接访问cout << bb._i1 << endl;bb.func2();
}

在这里插入图片描述

基类成员 / 继承方式public 继承protected 继承private 继承
public 成员派生类中的public成员派生类中的protected成员派生类中的private成员
protected 成员派生类中的protected成员派生类中的protected成员派生类中的private成员
private 成员在派生类中不可见在派生类在不可见在派生类在不可见
  1. 基类private 成员在派生类中无论以什么方式继承都是不可见的;(这里不可见指的就是私有成员还是被继承到派生类当中去,但是语法上,无论是在派生类里面还是类外面都不能直接去访问它。
  2. ​ 基类private成员在派生类中不能被访问;如果基类成员只是不想在类外直接被访问,而在派生类中能被访问,就定义为protected。(可以看出保护成员限定符protected就是因继承而出现的)。
  3. ​ 根据上面表格,可以发现: 基类的private 成员在派生类当中都是不可见的。基类的其他成员在派生类发访问方式 就等与 成员在基类的访问限定符与继承方式之中最小的 public > protected > private
  4. ​ 在实际应用中,一般都是使用public 继承,很少使用protectedprivate 继承(不推荐,因为protectedprivate 继承下来的成员只能在派生类中使用,实际中扩展维护性不是很强)。

3、继承类模板

​继承类模板:
如果继承的基类是类模板,则需要指定类域;否则就会报错。

这里使用继承来实现一下stack

template<class T>
class stack : public vector<T>
{//这里编译器是按需进行实例化,这里只是实例化出vector<T>//里面对应的成员函数等,都是按需实例化,在需要时才进行实例化//所以需要指定类域来访问vector<T> 的成员函数void push(const T& x){// 模版是按需实例化,push_back等成员函数未实例化,所以找不到vector<T>::push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}
};

三、基类和派生类之间的转换

  • public 继承的派生类对象,可以赋值给基类的 指针/引用 ;这种我们可以形象的称为**切片(或者切割) **,简单来说就是将派生类中基类的那一部分切出来,基类的指针/引用 指向派生类中切出来的那一部分。
  • 基类对象不能赋值给派生类对象
  • 基类的指针/引用 可以通过强制转换赋值给派生类的指针和引用 ,但必须是基类的指针指向派生类对象时才是安全的。(这里如果基类是多态类型,可以使用RTTIdynamic_cast 进行识别后进行安全转换。以后再了解这部分内容)

在这里插入图片描述

void test2()
{Student sobj;// 1.派⽣类对象可以赋值给基类的指针/引⽤Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错//sobj = pobj;
}

四、继承中的作用域

1、隐藏规则

  • 在继承中,基类和派生类都有独立的作用域。
  • 基类和派生类中有同名函数时,派生类将屏蔽基类的同名函数的直接访问(这种情况叫做隐藏)(可以和后面多态中的覆盖/重写对比记忆,在多态章节的博客中详细讲解)。
  • 注意: 对于成员函数,只要函数名相同就构成隐藏。
  • 实际中最好不定义同名函数。
class person
{
protected:string _name = "小晓";int _num = 111;
};
class student :public person
{
public:void test(){cout << "_name: " << _name << endl;cout << "_num: " << _num << endl;cout << "person::_num: " << person::_num << endl;}
protected:int _num = 999;
};
void test3()
{student s1;s1.test();
}
int main()
{test3();return 0;
}

2、相关选择题解析

在这里插入图片描述


class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" << i << endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};

​ 首先,对于成员函数,只要函数名相同就构成隐藏,使用A类与B类中fun 函数构成隐藏。

对于第二道题,我第一时间想法:正常运行,(个人理解:B类中继承了A类的fun() 成员函数,又实现了自己的成员函数fun(int) 形成了函数重载);很显然这种理解是错误的,(在继承中,派生类中会屏蔽基类中同名函数的直接访问);我们无法通过派生类对象直接访问基类的成员函数。

在这里插入图片描述

五、派生类的默认成员函数

1、默认成员函数

现在再来看一下默认成员函数:
在这里插入图片描述

所谓默认成员函数(6个),就是我们不写编译器会自动生成;那在派生类当中,这写默认成员函数是如何生成的呢。

  1. 派生类的构造函数必须调用基类的构造函数来初始化基类的那部分成员,如果基类没有默认构造函数,就必须在派生类构造函数的初始化列表显示调用。
  2. 派生类的拷贝构造函数必须调用基类的构造函数完成基类的拷贝初始化。
  3. 派生类的operator=函数必须要调用基类的operator=函数完成基类的赋值(**注意:**派生类的operator=隐藏了基类的operator=函数,所以在调用时需要指定类域)。
  4. 派生类的析构函数会在被调用完成之后自动调用基类的析构函数清理基类成员;(这样才能保证派生类对象先清理派生类的那一部分成员,再清理基类成员这一顺序)。
  5. 派生类对象初始化先调用基类的构造函数,再调用派生类的构造函数。
  6. 派生类对象析构清理先调用派生类析构再调用基类的析构。
  7. 在多态的一些场景中,需要对析构函数构成重写,重写的条件是函数名相同(学习多态时了解);编译器会对析构函数名进行特殊处理,处理成destructor,所以基类的析构函数不加virtual 的情况下,派生类析构函数和基类析构函数构成隐藏。

2、番外篇:实现一个不能被继承的类

方法一: 基类的构造函数私有,派生类的构造必须调用基类的构造函数,但是基类的构造函数私有化,派生类就不能调用,就无法实例化出对象。

方法二 C++11 新增关键字final, final修饰基类,派生类就不能继承的。

方法一:

//基类构造函数私有化
class Test
{
public:void func(){cout << "构造函数私有化,派生类无法实例化出对象" << endl;}
protected:int _i = 1;
private:Test(){}
};class Fun : public Test
{
public:Fun(){}
private:char _ch;
};

在这里插入图片描述

方法二:

//final修饰
class Base final
{
public:void fun(){cout << "final修饰,无法被继承" << endl;}
protected:int _i;char _ch;
};class Div : public Base
{};

在这里插入图片描述

六、继承中的友元

首先,友元关系不能被继承;也就基类的友元函数无法访问派生类的私有和保护成员。

class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Func(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}

在这里插入图片描述

七、继承中的静态成员

​ 基类定义了static 静态成员,在整个继承体系中有且只有一个这样的成员;无论有多少派生类都只有一个static 静态成员。

其实这个也很好理解,静态成员存储在常量区,只存在一个。

八、多继承与菱形继承

1、继承模型

单继承: 一个派生类只有一个基类

多继承: 应该派生类有两个或以上直接基类;(多继承对象在内存中的模型:先继承的在基类在前面,后继承的基类在后面,派生类成员放到最后。

**菱形继承: ** 菱形继承属于多继承的一种特殊情况,菱形继承存在数据冗余和二义性的问题;在Assistant 的对象中,Person 类的成员会存在两份。(多继承,一定会存在菱形继承;java 中不支持多继承就规避了这个问题;所以不推荐设计出这种菱形继承的模型)。

在这里插入图片描述

在这里插入图片描述

2、虚继承

虚继承可以说是解决菱形继承这个问题的;

​ 很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有

菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可

以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。

class Person
{
public:string _name;int _age;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;
}

简单来说,不推荐设计出菱形继承。(设计出菱形继承就会复杂很多,所以还是尽量避免)。

3、IO库中的虚拟菱形继承

在这里插入图片描述

template < class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios < CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};

九、继承与组合

  • public继承是一种is-a;就是每一个派生类对象都是一个基类对象
  • 组合呢,是has-a的关系;这里假设B组合了A,每一个B对象中都有一个A对象。
  • 继承中允许根据基类的实现来定义派生类的实现;这种通过生成派生类的复用通常被称为白箱复用;(**白箱复用相对于可视性而言:**在继承方式中,基类的内部细节对派生类可见;继承一定程度上破坏了基类的封装,基类的改变对派生类有很大影响;基类和派生类之间耦合性高,依赖性很强)。
  • 组合,是类继承之外的另一种复用选择;新的更复杂的功能可以通过组合对象来实现,对象组合要求被组合的对象具有良好定义的接口;这种复用风格被称为黑箱复用;(对象的内部细节不可见,组合类之间没有很强的依赖关系,耦合度低,使用对象组合,有助于保持每个类的封装。
  • 优先去使用组合,而不是继承,组合耦合度低,代码维护性好;不过类之间如果关系适合继承(is-a)那就使用继承,多态也要实现继承;

继承部分到这里就结束了,感谢各位的支持,继续加油!!!

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

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

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

相关文章

安卓-碎片的使用入门

1.碎片(Fragment)是什么 Fragment是依赖于Activity的&#xff0c;不能独立存在的,是Activity界面中的一部分&#xff0c;可理解为模块化的Activity,它能让程序更加合理和充分地利用大屏幕的空间&#xff0c;因而在平板上应用得非常广泛. Fragment不能独立存在&#xff0c;必须…

WRF-Chem模式安装、环境配置、原理、调试、运行方法;数据准备及相关参数设置方法

大气污染是工农业生产、生活、交通、城市化等方面人为活动的综合结果&#xff0c;同时气象因素是控制大气污染的关键自然因素。大气污染问题既是局部、当地的&#xff0c;也是区域的&#xff0c;甚至是全球的。本地的污染物排放除了对当地造成严重影响外&#xff0c;同时还会在…

Admin.NET框架使用宝塔面板部署步骤

文章目录 Admin.NET框架使用宝塔面板部署步骤&#x1f381;框架介绍部署步骤1.Centos7 部署宝塔面板2.部署Admin.NET后端3.部署前端Web4.访问前端页面 Admin.NET框架使用宝塔面板部署步骤 &#x1f381;框架介绍 Admin.NET 是基于 .NET6 (Furion/SqlSugar) 实现的通用权限开发…

.net —— Razor

文章目录 项目地址一、创建一个Razor项目1.1 创建项目1.2 创建项目所需文件夹1.3 配置项目二、创建Category页面2.1 创建Category的展示页面2.2 增删改2.2.1 创建Edit的razor视图项目地址 教程作者:教程地址:代码仓库地址:所用到的框架和插件:dbt airflow一、创建一个Razo…

C语言——指针初阶(三)

目录 一.指针-指针 代码1&#xff1a; 运行结果&#xff1a; 代码2&#xff1a; 运行结果&#xff1a; 代码3&#xff1a; 运行结果&#xff1a; 二.指针数组 例&#xff1a; 往期回顾 一.指针-指针 指针减去指针的前提&#xff1a;两个指针指向同一块空间。 指针减去指针…

spring boot2.7集成OpenFeign 3.1.7

1.Feign Feign是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign&#xff0c;请创建一个接口并对其进行注释。它具有可插入注释支持&#xff0c;包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持&…

大R玩家流失预测在休闲社交游戏中的应用

摘要 预测玩家何时会离开游戏为延长玩家生命周期和增加收入贡献创造了独特的机会。玩家可以被激励留下来&#xff0c;战略性地与公司组合中的其他游戏交叉链接&#xff0c;或者作为最后的手段&#xff0c;通过游戏内广告传递给其他公司。本文重点预测休闲社交游戏中高价值玩家…

【linux学习指南】Linux进程信号产生(三) 硬件异常除零出错?野指针异常?core文件

文章目录 &#x1f4dd;前言&#x1f320;模拟除0&#x1f309;除0出错&#xff1f;&#x1f309;野指针异常? &#x1f320;⼦进程退出coredump&#x1f309;Core Dump &#x1f6a9;总结 &#x1f4dd;前言 硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前…

计算机毕业设计Python异常流量检测 流量分类 流量分析 网络流量分析与可视化系统 网络安全 信息安全 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

基于SpringBoot的电脑配件销售系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

win10系统部署RAGFLOW+Ollama教程

本篇主要基于linux服务器部署ragflowollama&#xff0c;其他操作系统稍有差异但是大体一样。 一、先决条件 CPU ≥ 4核&#xff1b; RAM ≥ 16 GB&#xff1b; 磁盘 ≥ 50 GB&#xff1b; Docker ≥ 24.0.0 & Docker Compose ≥ v2.26.1。 如果尚未在本地计算机&#xff…

docker使用(镜像、容器)

docker基础使用 文章目录 前言1.镜像操作1.1命令介绍1.2.案例实操1.2.1查找镜像1.2.2下载镜像1.2.3查看当前镜像 2.容器操作2.1命令2.1.1容器创建与启动2.1.2. 容器查看2.1.3. 容器操作2.1.4. 容器删除2.1.5. 容器日志2.1.6. 容器内文件操作2.1.7. 容器内命令执行2.1.8. 其他常…

服务器数据恢复—raid6阵列硬盘被误重组为raid5阵列的数据恢复案例

服务器存储数据恢复环境&#xff1a; 存储中有一组由12块硬盘组建的RAID6阵列&#xff0c;上层linux操作系统EXT3文件系统&#xff0c;该存储划分3个LUN。 服务器存储故障&分析&#xff1a; 存储中RAID6阵列不可用。为了抢救数据&#xff0c;运维人员使用原始RAID中的部分…

AIGC训练效率与模型优化的深入探讨

文章目录 1.AIGC概述2.AIGC模型训练效率的重要性3.模型优化的概念与目标4.模型优化策略4.1 学习率调节4.2 模型架构选择4.3 数据预处理与增强4.4 正则化技术4.5 量化与剪枝 5.代码示例6.结论 人工智能领域的发展&#xff0c;人工智能生成内容&#xff08; AIGC&#xff09;越来…

Ubuntu Server 22.04.5 从零到一:详尽安装部署指南

文章目录 Ubuntu Server 22.04.5 从零到一&#xff1a;详尽安装部署指南一、部署环境二、安装系统2.1 安装2.1.1 选择安装方式2.1.2 选择语言2.1.3 选择不更新2.1.4 选择键盘标准2.1.5 选择安装版本2.1.6 设置网卡2.1.7 配置代理2.1.8 设置镜像源2.1.9 选择装系统的硬盘2.1.10 …

Qt之样式表设置总结。。。持续更新

参考文章链接如下: Qt样式表之一:Qt样式表和盒子模型介绍 Qt样式表之二:QSS语法及常用样式 Qt样式表之三:实现按钮三态效果的三种方法 Qt样式表之一:QSS名词解释 Qt样式表之二:常用控件qss Qt样式表之三:QSS奇技淫巧 样式表介绍 Qt样式表是一个可以自定义部件外观的十…

ASP.NET Web(.Net Framework) Http服务器搭建以及IIS站点发布

ASP.NET Web&#xff08;.Net Framework&#xff09; Http服务器搭建以及IIS站点发布 介绍创建ASP.NET Web &#xff08;.Net Framework&#xff09;http服务器创建项目创建脚本部署Http站点服务器测试 Get测试编写刚才的TestWebController.cs代码如下测试写法1测试写法2 Post测…

贪心算法基础解析

贪心算法 贪心算法的核心思想是&#xff1a;在每个阶段选择当前状态下最优的选择&#xff0c;从而希望通过局部最优的选择达到全局最优。 53. 最大子数组和 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#…

【初阶数据结构和算法】二叉树顺序结构---堆的定义与实现(附源码)

文章目录 一、堆的定义与结构二、堆的实现1.堆的初始化和销毁堆的初始化堆的销毁 2.向上调整算法和入堆向上调整算法入堆 3.向下调整算法和出堆顶数据向下调整算法出堆 4.堆的有效数据个数和判空堆的有效数据个数堆的判空 5.取堆顶数据 三、堆的源码 一、堆的定义与结构 本篇内…

【北京迅为】iTOP-4412全能版使用手册-第二十章 搭建和测试NFS服务器

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…