c++:面向对象三大特性--继承

在这里插入图片描述

面向对象三大特性--继承

  • 一、继承的概念及定义
    • (一)概念
    • (二)继承格式
      • 1、继承方式
      • 2、格式写法
      • 3、派生类继承后访问方式的变化
    • (三)普通类继承
    • (四)类模板继承
  • 二、基类和派生类的转换
    • (一)基类转换派生类
    • (二)派生类转换基类
  • 三、几个重要细节
    • (一)继承与作用域
      • 1、作用域
      • 2、隐藏
    • (二)继承与友元
    • (三)继承与静态成员
  • 四、继承中派生类的构造函数
  • 五、多继承与菱形继承
    • (一)多继承
      • 多继承的指针偏移问题
    • (二)菱形继承
    • (三)虚继承
  • 六、继承和组合
    • 结束语:

一、继承的概念及定义

(一)概念

继承是⾯向对象程序设计使代码可以复用的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。

(二)继承格式

1、继承方式

我们前面对类的成员有三种限制方式,这里也就对应了三种继承方式
在这里插入图片描述

2、格式写法

在这里插入图片描述

3、派生类继承后访问方式的变化

1、通过表格可以发现,如果是private成员,那么无论哪种继承方式都不可以访问到这个权限
2、此外,structclass这两个关键字在继承时也有差距,struct默认继承方式为公有,而class默认继承方式为私有。

我们如果将权限的大小定义为 public > protected > private, 那么其余访问方式变化就是将大于该继承方式的权限降到继承方式的权限即可

在这里插入图片描述

(三)普通类继承

这里用到的是继承最基本的语法,采用public继承,那么除了父类的private变量不可访问以外,成员的权限保持不变。

class Person
{
public:void Print(){cout << _name << endl;cout << _age << endl;}
protected:string _name = "张三"; // 姓名
private:int _age = 18; // 年龄
};class Student : public Person
{
public:void func(){Print();}
protected:int _stunum; // 学号
};

(四)类模板继承

在之前我们实现stack时,采用的是新建了一个容器类型,在这里我们亦可以采用继承的方式来实现。
需要注意的是,派生类在继承时,如果需要访问父类的成员函数,需要指定类域,模板的成员函数采用的是按需实例化

namespace wgm
{template<class T>class stack : public std::vector<T>{public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,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();}};
}

二、基类和派生类的转换

(一)基类转换派生类

1、基类对象不能赋值给派⽣类对象。
2、基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针
是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type Information)dynamic_cast 来进⾏识别后进⾏安全转换。

(二)派生类转换基类

1、public继承的派⽣类对象 可以赋值给基类的指针/基类的引⽤。这⾥有个形象的说法叫切⽚或者切
。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分。

值得注意的是,之前在隐式类型转换时会生成临时变量,因此在应用时需要加上const,而在切片时不会生成中间的临时变量

class Person
{
protected:string _name; // 姓名string _sex; // 性别
public:int _age = 18; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{string s1 = "11111";const string& s2 = "11111";Student sobj;// 赋值兼容转换,特殊处理// 1.派生类对象可以赋值给基类的指针/引用Person* pp = &sobj;Person& rp = sobj;rp._age++;return 0;
}

在这里插入图片描述

接下来通过下面的例子发现,继承后的基类私有变量虽然访问不到,但是我们可以发现它在派生类的对象中依旧占据相应的空间,而经过赋值兼容转换变量的大小为基类的大小

在这里插入图片描述

在这里插入图片描述

接下来更加深层的来了解赋值兼容,发现基类的指针或引用在调用重名函数的时候,调用的是父类的函数,而派生类调用时因为隐藏的特点,派生类对象调用的是派生类的函数

class A
{
public:void func(){cout << "A::func()" << endl;}protected:int _a;int _b;
private:int _c;
};class B : public A
{public:void func(){cout << "B::func()" << endl;}public:int _d;
};int main() {B obj_b;A* ptr_a = &obj_b;A& ref_a = obj_b;obj_b.func();ptr_a->func();ref_a.func();return 0;
}

2、子类的变量可以复制给父类。

	Person pobj = sobj;

在这里插入图片描述

三、几个重要细节

(一)继承与作用域

1、作用域

在继承体系中基类和派⽣类都有独⽴的作⽤域。

2、隐藏

派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

(二)继承与友元

在继承时,友元关系是不接受继承的。所以如果友元函数需要访问派生类的成员,需要重新声明友元。

(三)继承与静态成员

在继承后,静态成员变量始终只有基类在定义的这一份。通过下面的代码可以发现,我们可以用类域加静态变量的方式来访问静态变量,但是打印的地址是同一份。

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;cout << &p._count << endl;cout << &s._count << endl;cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}

在这里插入图片描述

四、继承中派生类的构造函数

  1. 派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。基类没有默认的构造函数必须在派⽣类构造函数的初始化列表阶段显⽰调⽤
  2. 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
  3. 派⽣类的operator=必须要调⽤基类的operator=。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以指定基类作⽤域显⽰调⽤基类的operator=
  4. 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。才能保证先清理派⽣类成员再清理基类成员。因为多态中⼀些场景析构函数需要构成重写。,那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系
class Person
{
public:Person(const char* name): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}// destructor()~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num, const char* address, const char* name):_num(num), _address(address), Person(name){cout << "Student()" << endl;}Student(const Student& s): Person(s), _num(s._num), _address(s._address){cout << "Student(const Student& s)" << endl;}Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){_num = s._num;_address = s._address;Person::operator=(s);}return *this;}// destructor()~Student(){// 不需要写,子类析构函数结束后,会自动调用父类析构//Person::~Person();cout << "~Student()" << endl;}protected:int _num; //学号string _address;
};

五、多继承与菱形继承

(一)多继承

单继承:⼀个派⽣类只有⼀个直接基类时称为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称为多继承

多继承的指针偏移问题

多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯
在这里插入图片描述
通过上面的例子,我们可以清晰的认识到基类在派生类的储存情况。

(二)菱形继承

菱形继承:菱形继承是多继承的⼀种特殊情况,有数据冗余和⼆义性的问题

在这里插入图片描述

class Person
{
public:string _name; // 姓名
};class Student : public Person
{
protected:int _num; //学号
};class Teacher : public Person
{
protected:int _id; // 职工编号
};//给类加上 virtual 关键字,解决菱形继承造成的二义性和数据冗余。//class Student : virtual public Person
//{
//protected:
//	int _num; //学号
//};
//
//class Teacher : virtual public Person
//{
//protected:
//	int _id; // 职工编号
//};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};int main() {Assistant obj;obj.Student::_name = "张三";obj.Teacher::_name = "李四";return 0;
}

在这里插入图片描述
通过调试窗口,可以发现我们在继承时同时继承了来自Person和来自Teacher_name我们在写代码时无法处理这个二义性,同时也形成了数据冗余。

(三)虚继承

为了解决这个现象,我们只需要在继承同一个基类成员的派生类加上一个virtual关键字,底层会自行加工,使得我们后面访问的_name只是一份数据。

class Person
{
public:string _name; // 姓名
};//给类加上 virtual 关键字,解决菱形继承造成的二义性和数据冗余。
class Student : virtual public Person
{
protected:int _num; //学号
};class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};int main() {Assistant obj;obj.Student::_name = "张三";obj.Teacher::_name = "李四";return 0;
}

下列窗口显示出来的_name实则是同一份数据,最开始指定类域Student::初始化_name为张三
在这里插入图片描述
我们通过Teacher::修改数据为李四,那么数据被修改为李四。
在这里插入图片描述

切记,尽量不用使用菱形继承,因为virtual关键字在解决问题的同时造成了效率的降低,代价有点大。

六、继承和组合

继承组合
定义public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
复用方式白箱复用:在继承⽅式中,基类的内部细节对派⽣类可⻅⿊箱复⽤:通过调用对象的接口实现,对象的内部细节是不可⻅的
耦合度继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度高组合类之间没有很强的依赖关系,耦合度低

我们可以发现,组合的好处要大于继承,在两种都可以的情况下,优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。

结束语:

感谢一直以来支持的朋友,支持一路走来披荆斩棘的道友,或许不识,一路同行!

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

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

相关文章

【C语言】web workers

请解释一下什么是Web Workers&#xff0c;以及它在哪些场景下会被使用。 Web Workers是一种HTML5技术&#xff0c;它允许在浏览器后台线程中运行脚本&#xff0c;从而实现了JavaScript的异步处理。Web Workers创建了独立于主线程的执行上下文&#xff0c;可以执行计算密集型任…

关于网络安全攻防知识

DNS 劫持 什么是DNS劫持&#xff1f; DNS劫持又叫域名劫持&#xff0c;&#xff08;劫持了路由器或域名服务器等&#xff09;&#xff0c;篡改了域名的解析结果&#xff0c;使得指向该域名的IP指向IP&#xff0c;你想访问正经网站结果给你跳到一个不正经的网站&#xff0c;实现…

基于Boost库的搜索引擎

本专栏内容为&#xff1a;项目专栏 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;基于Boots的搜索引擎 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识…

Pgsql:json字段查询与更新

1.查询json字段的值 SELECT attribute_data->>设施类别 mycol, * FROM gis_coord_data WHERE attribute_data->>设施类别阀门井 查询结果如下&#xff1a; 2.更新json字段中的某个属性值 UPDATE gis_coord_data SET attribute_data(attribute_data::jsonb ||{&quo…

【eNSP】动态路由协议RIP和OSPF

动态路由RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;和OSPF&#xff08;Open Shortest Path First&#xff0c;开放式最短路径优先&#xff09;是两种常见的动态路由协议&#xff0c;它们各自具有不同的特点和使用场景。本篇会对这两种协…

Linux——基础命令(1)

目录 一、认识Linux 终端命令格式 查阅命令帮助信息 -help 辅助操作 自动补全 清屏和查看当前工作目录 二、基本命令 文件和目录常用命令 1.ls-查看文件与目录 2.cd切换目录 &#xff08;1&#xff09;touc创建文件或修改文件时间 &#xff08;2&#xff09;mkdir创…

leetcode - LRU缓存

什么是 LRU LRU (最近最少使用算法), 最早是在操作系统中接触到的, 它是一种内存数据淘汰策略, 常用于缓存系统的淘汰策略. LRU算法基于局部性原理, 即最近被访问的数据在未来被访问的概率更高, 因此应该保留最近被访问的数据. 最近最少使用的解释 LRU (最近最少使用算法), 中…

基于springboot的HttpClient、OKhttp、RestTemplate对比

HttpClient详细 Httpclient基础&#xff01;&#xff01;&#xff01;&#xff01;实战训练&#xff01;&#xff01;&#xff01;&#xff01;-CSDN博客 OKhttp使用 OKhttp导包 <!-- ok的Http连接池 --><dependency><groupId>com.squareup.okhttp3</g…

【Python】九大经典排序算法:从入门到精通的详解(冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序、计数排序、基数排序、桶排序)

文章目录 1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 选择排序&#xff08;Selection Sort&#xff09;3. 插入排序&#xff08;Insertion Sort&#xff09;4. 归并排序&#xff08;Merge Sort&#xff09;5. 快速排序&#xff08;Quick Sort&#xff09;6. 堆排序&…

【PyTorch】(基础三)---- 图像读取和展示

图像读取和展示 pytorch本身并不提供图像的读取和展示功能&#xff0c;利用pytorch执行计算机视觉任务的时候&#xff0c;通常是利用opencv等工具先进行图像处理&#xff0c;然后将结果转化成tensor类型传递给pytorch&#xff0c;在pytorch执行之后&#xff0c;也可以将tensor…

Diffusion异常检测相关论文及代码整理

扩散模型&#xff08;Diffusion Models&#xff09;是一种生成模型&#xff0c;广泛用于图像生成、文本生成等领域。在异常检测任务中&#xff0c;扩散模型也可以被用来识别和检测异常数据点。该文章对近几年利用扩散模型进行异常检测的文章进行了整理&#xff1a; 2024 1. A…

蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01;! ! ! ! &#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 动态规划 三、括号序列 【问题描述】 给定一个括号序列&#xff0c;要求尽可能少地添加若干括号使得括号序列变得合…

LLM PPT Translator

LLM PPT Translator 引言Github 地址UI PreviewTranslated Result Samples 引言 周末开发了1个PowerPoint文档翻译工具&#xff0c;上传PowerPoint文档&#xff0c;指定想翻译的目标语言&#xff0c;通过LLM的能力将文档翻译成目标语言的文档。 Github 地址 https://github.…

三格电子—EtherNet IP转Modbus RTU网关

EtherNet/IP转Modbus RTU网关 SG-EIP-MOD-210 产品用途 SG-EIP-MOD-210网关可以实现将Modbus接口设备连接到 EtherNet/IP网络中。用户不需要了解具体的Modbus和 EtherNet/IP协议即可实现将Modbus设备挂载到 EtherNet/IP接口的PLC上&#xff0c;并和Modbus设备进行数据交互。拓…

【计算机网络】核心部分复习

目录 交换机 v.s. 路由器OSI七层更实用的TCP/IP四层TCPUDP 交换机 v.s. 路由器 交换机-MAC地址 链接设备和设备 路由器- IP地址 链接局域网和局域网 OSI七层 物理层&#xff1a;传输设备。原始电信号比特流。数据链路层&#xff1a;代表是交换机。物理地址寻址&#xff0c;交…

前端网络请求:从 XMLHttpRequest 到 Axios

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vue篇专栏内容:前端网络请求&#xff1a;从 XMLHttpRequest 到 Axios 前言 在网络应用中&#xff0c;前后端的数据…

计算机毕业设计Python+大模型美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

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

Element UI 打包探索【2】

目录 第三个命令 第四个命令 第五个命令 第六个命令 第七个命令 cross-env BABEL_ENV babel 第八个命令 总结 书&#x1f4da;接上文Element UI 打包探索【1】我们继续来看 第三个命令 "lint": "eslint src/**/* test/**/* packages/**/* build/**/* …

一篇保姆式centos/ubuntu安装docker

前言&#xff1a; 本章节分别演示centos虚拟机&#xff0c;ubuntu虚拟机进行安装docker。 上一篇介绍&#xff1a;docker一键部署springboot项目 一&#xff1a;centos 1.卸载旧版本 yum remove docker docker-client docker-client-latest docker-common docker-latest doc…

游戏引擎学习第23天

实时代码编辑功能的回顾 当前实现的实时代码编辑功能已经取得了显著的成功&#xff0c;表现出强大的性能和即时反馈能力。该功能允许开发者在修改代码后几乎立即看到变化在运行中的程序中体现出来&#xff0c;极大提升了开发效率。尽管目前的演示内容较为简单&#xff0c;呈现…