【C++高阶】C++继承学习手册:全面解析继承的各个方面
📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:模板进阶
🌹🌹期待您的关注 🌹🌹
继承
- 📖1. 继承的概念及定义
- ⛰️继承的概念
- 🌄继承定义
- 📙2. 基类和派生类对象赋值转换
- 📕3. 继承中的作用域
- 🎩成员变量隐藏
- 🎈成员函数隐藏
- 📚4. 派生类的默认成员函数
- 🧩默认成员函数
- 🧩派生类默认函数特征
- 📒5. 友元与静态成员变量
- 🍂友元
- 🍁静态成员
- 📜6. 多继承
- 🌞菱形继承
- 🌙虚拟继承
- ⭐虚拟继承解决数据冗余和二义性的原理
- 🔥7. 总结
前言: 当我们踏上C++编程的旅程时,继承无疑是一个无法回避且至关重要的概念。作为面向对象编程的三大特性之一,继承不仅让我们能够创建出层次清晰、结构合理的代码,还极大地提高了代码的可重用性和可维护性。在本文中,我们将一起深入探讨C++继承的奥秘,从基础概念到高级应用,逐步揭开它的神秘面纱
C++继承允许我们定义一个基类(或称为父类),并从这个基类中派生出新的类(称为派生类、子类)。派生类会继承基类的成员和成员函数,同时还可以添加自己的成员和成员函数。这种能力使得我们能够构建出复杂的类层次结构,实现代码的模块化和复用
在本文的学习中我们不仅仅要了解继承的基本概念。在实际编程中,我们还需要掌握如何正确使用继承、如何避免常见的继承陷阱、以及如何利用继承来优化我们的代码结构。因此,本文将带领大家从多个角度全面学习C++继承,包括继承的语法规则、访问控制、构造函数与析构函数的调用、多重继承与菱形继承等问题
让我们一起踏上学习C++继承的旅程吧!
📖1. 继承的概念及定义
⛰️继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
继承代码示例
class A
{
public:void func(){cout << "A::func()" << endl;}
protected:int _a = 10;
};// 继承后父类A的成员_a(成员函数+成员变量)都会变成子类的一部分
class B : public A
{
public:// ......
protected:int _b = 100;
};int main()
{A a ;B b;a.func();b.func(); // b可以调用A中的成员函数return 0;
}
🌄继承定义
我们从刚刚的代码示例可以看到A是基类(父类),B是派生类(子类)
定义格式
注意:在定义继承的时候继承方式可以省略不写,如果不写则是根据基类的定义来决定默认继承方式,但是建议定义时带上继承方式
class定义的类默认private继承,struct定义的类默认public继承
继承关系和访问限定符
继承基类成员访问方式的变化
继承方式和访问限定符都有三种,虽然它们组合一共有9中能使用的方法,但是我们最常用的只有红色框里面的两种用法
这里我们有以下几点需要注意:
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
📙2. 基类和派生类对象赋值转换
关于赋值规则这里我们先提两点:
- 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去
- 基类对象不能赋值给派生类对象
我们在讲C++入门知识的时候讲过,引用类型不同的变量时,会产生一个临时变量,临时变量具有常性,需要const修饰,但是在继承中就不需要const修饰
代码示例
int main()
{int c = 1;double d = 1.1;const int& r = d; // 中间产生了一个临时变量,临时变量具有常性,需要const修饰B b;A a = b; // 子类可以赋值给基类// b = a; // false, 基类不可以赋值给子类A& ra = b; // is-a 的关系中间不会产生临时对象,父子类的赋值兼容规则(切割/切片)return 0;
}
继承中的对象是is-a
的关系,它们中间并不会产生临时对象,这就是父子类的赋值兼容规则(切割/切片)
📕3. 继承中的作用域
关于作用域的注意事项:
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
- 注意在实际中在继承体系里面最好不要定义同名的成员
🎩成员变量隐藏
当继承的基类与子类有同名的成员变量时,不指定的话,会调用子类的成员变量
代码示例
class A
{
protected:int _a = 10;
};class B : public A
{
public:void Print(){cout << "_a:" << _a << endl;// cout << "A: _a:" << A::_a << endl; // 要想成功打印A类的元素必须要指定cout << "_b:" << _b << endl;}
protected:int _a = 99;int _b = 100;
};int main()
{B b;// 成员变量同名// A 和 B中的 _a 构成隐藏b.Print(); // // _a = 99 , _b = 100; 就近原则return 0;
}
🎈成员函数隐藏
在继承中,同名函数并不会构成函数重载,因为他们在不同的作用域,每个类都是独立的,成员函数满足函数名相同就构成隐藏
代码示例
class A
{
public:void func(){cout << "func()" << endl;}
protected:int _a = 10;
};class B : public A
{
public:// void func(int b){cout << "func(int b)" << endl;}
protected:int _b = 100;
};int main()
{B b;// 成员函数同名// A 和 B中的 func() 构成隐藏b.func(); // 打印“func(int b)”
}
📚4. 派生类的默认成员函数
🧩默认成员函数
默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个
相关文章:默认成员函数
🧩派生类默认函数特征
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
- 派生类的operator=必须要调用基类的operator=完成基类的复制
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
- 派生类对象初始化先调用基类构造再调派生类构造
- 派生类对象析构清理先调用派生类析构再调基类的析构
- 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系
综上所述:关于基类和子类的调用顺序,一般情况都是先父后子,但是析构必须先子后父,来避免析构完父类之后,子类出错
继承默认函数的实现代码示例
class A
{
public:A(){}A(int a):_a(a){cout << "A()" << endl;}A(const A& a):_a(a._a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;if (&a != this){_a = a._a;}return *this;}~A(){cout << "~A()" << endl;}
protected:int _a = 10;
};class B : public A
{
public:B(){}B(int a, int b)// :_a(a) // _a不是基类成员不能这样初始化:A(a),_b(b){cout << "B()" << endl;}B(const B& b)// :_a(a) // _a不是基类成员不能这样初始化:A(b), _b(b._b){cout << "B(const A& a, const B& b)" << endl;}B& operator=(const B& b){cout << "B& operator=(const B& b)" << endl;if (&b != this){// 需要调用A类的 operator=A::operator=(b);_b = b._b;}return *this;}// 析构函数会先析构父类,而有时候先析构父类,子类会出事// 不需要显式调用父类析构~B(){cout << "~B()" << endl;}
protected:int _b = 100;
};
int main()
{B b1(1, 100);B b2(b1);B b3(2, 200);b1 = b3;return 0;
}
📒5. 友元与静态成员变量
🍂友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,因为朋友的朋友不一定也是自己的朋友,如果基类,子类都想使用必须都在各自的域里面声明
代码示例
class A
{
public:friend void Print(const A& a, const B& b);
protected:int _a = 10;
};class B : public A
{
public://
protected:int _b = 100;
};void Print(const A& a, const B& b)
{cout << a._a << endl;cout << b._b << endl;
}int main()
{A a;B b;Print(a, b);
}
🍁静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
代码示例
class A
{
public:A(){++_count;}static int _count;
protected:int _a = 10;
};
int A::_count = 0;
class B : public A
{
public://
protected:int _b = 100;
};int main()
{A a;B b;cout << A::_count << endl;
}
📜6. 多继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
class B : public A
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
class D : public B , public C
菱形继承:菱形继承是多继承的一种特殊情况。
class B : public A
{......};
class C : public A
{......};
class D : public B , public C
🌞菱形继承
class A
{
protected:int _a = 1;
};
class B :public A
{
protected:int _b = 2;
};
class C :public A
{
protected:int _c = 3;
};
class D :public B, public A
{
protected:int _d = 4;
};
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在D的对象中_a成员会有两份,我们在访问的时候无法明确知道访问的是哪一个,必须要显示指定访问哪个父类的成员,但是数据冗余任然无法解决!
🌙虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在A和B的继承A时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用
代码示例
class A
{
protected:int _a = 1;
};
class B : virtual public A
{
protected:int _b = 2;
};
class C : virtual public A
{
protected:int _c = 3;
};
class D : public B, public A
{
protected:int _d = 4;
};
⭐虚拟继承解决数据冗余和二义性的原理
- 虚拟继承通过将共同的祖先类(即虚基类)的拷贝在派生类对象中只保留一份,来解决这个问题。具体来说,虚拟继承会在内存中创建一个虚基表,并在派生类对象中存储一个指向这个虚基表的指针(即虚基表指针)。虚基表中存的偏移量。通过偏移量可以找到下面的A,而无需在派生类对象中多次存储这些数据成员。因此,虚拟继承通过减少重复存储的数据成员来消除数据冗余
- 虚拟继承通过改变派生类访问虚基类成员的方式来解决这个问题。在虚拟继承中,派生类对象通过虚基表指针来访问虚基类(即共同祖先类)的成员。由于虚基表中存储了虚基类成员的地址,因此派生类对象可以明确地知道应该访问哪个虚基类成员,从而消除了二义性。
加上表中偏移量可以找到最底下的A
🔥7. 总结
回顾学习过程,我们学会了如何定义基类与派生类,掌握了访问控制规则,理解了构造函数与析构函数在继承中的作用,还探讨了多重继承及其带来的挑战。这些知识不仅丰富了我们的编程技能,更为我们解决实际问题提供了有力的工具
在结束对C++继承的学习之旅后,我们不禁感叹其强大的功能和灵活性。通过深入探究继承的基本概念、语法规则以及高级应用,我们逐渐揭开了其背后的奥秘,并体验到了它在面向对象编程中的独特价值
学习C++继承并非一蹴而就的过程。它需要我们不断地实践、思考、总结和创新。在未来的编程之路上,我们将继续深化对继承的理解,探索其更多的应用场景和高级特性,如虚继承、接口继承等,我们也要认识到继承并非万能的。在使用继承时,我们需要权衡其带来的好处和潜在的风险,避免过度使用导致代码结构复杂、难以维护。我们应该根据具体的需求和场景,选择最合适的编程范式和工具!!!
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!
相关文章:
【C++高阶】C++继承学习手册:全面解析继承的各个方面
📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C “ 登神长阶 ” 🤡往期回顾🤡:模板进阶 🌹🌹期待您的关注 🌹🌹 继承 📖1. 继承的概念及定义…...
使用GPT-soVITS再4060下2小时训练声音模型以及处理断句带来的声音模糊问题
B站UP主视频 感谢UP主“白菜工厂1145号员工”的“熟肉”,我这篇笔记就不展示整一个训练和推理流程,重点写的4060该注意的一些事项。如何解决断句模糊的问题,在本篇笔记的最末尾。 相关连接: 原项目github UP主的说明文档 1、训…...
如何对stm32查看IO功能。
有些同学对于别人的开发板的资源,或者IO口,或者串口等资源不知道怎么分配。 方法1、看硬石、野火、正点原子的开发板,看下他们的例子,那个资源用什么。自己多看几个原理图,多看几个视频,做一下笔记。以后依…...
docker构建jdk17镜像
资料参考 参考自黑马教程:10.Docker基础-自定义镜像_哔哩哔哩_bilibili 更多详细语法声明,请参考官网文档:https://docs.docker.com/engine/reference/builder 初步准备 1、下载jdk17包(linux版),我这边版…...
Android Uri转File path路径,Kotlin
Android Uri转File path路径,Kotlin /*** URI转化为file path路径*/private fun getFilePathFromURI(context: Context, contentURI: Uri): String? {val result: String?var cursor: Cursor? nulltry {cursor context.contentResolver.query(contentURI, null…...
iOS界面设计要点:四大模块解析
UI设计不是艺术设计,这限制了我们从设备和现有技术开始设计。因此,熟悉每个平台的设计规则已经成为每个设计师的第一课,也是每个设计师必要的专业知识。 今天小边给您带来了iOS设计规范,希望帮助您快速熟悉iOS平台设计规范&#…...
数字取证技术(Digital Forensics Technology)实验课II
数字取证技术(Digital Forensics Technology)实验课II 本文是我本学期的教学课题目,不包含任何博客知识分享,无关的读者可忽略; 实验练习题 (♞思考):请对工作邮件进行签名;“problem3_1.txt"里存储的是由John Doe撰写的真实的邮件,而"problem3_2.txt"里存储的…...
Redis缓存的使用
1.缓存穿透 描述:查询数据在redis不存在,请求打到数据库 解决方法: 缓存空值 当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value"null",并设…...
力扣LCP 08.剧情触发时间
力扣LCP 08.剧情触发时间 前缀和 二分 对increase求前缀和 在前缀和数组上做二分 找到符合要求的最小时间 class Solution {public:vector<int> getTriggerTime(vector<vector<int>>& increase, vector<vector<int>>& requirements)…...
Elasticsearch-IndexTemplate和DynamicTemplate 有什么区别
Elasticsearch中的Index Template和Dynamic Template是两种不同的概念,它们在索引管理中扮演不同的角色: ### Index Template(索引模板) 1. **目的**:用于定义新索引的默认设置,包括映射、设置、别名等。 …...
list集合自定义排序
一、基本类型排序 1.list中只有数字或字符串 //升序排序 List<T> ,T为数字或字符串 Collections.sort(list); //降序排序 Collections.sort(list,Collections.reverseOrder());2.list中为对象 基于jdk.18 import lombok.Data;Data public class User {private int i…...
PHP Cookies:应用与管理
在Web开发中,Cookies是一种在客户端(通常是浏览器)存储少量数据的机制。PHP作为一种服务器端脚本语言,提供了对Cookies的全面支持,使得开发者可以轻松地设置、读取和删除Cookies。Cookies通常用于存储用户的会话信息&a…...
【GD32F303红枫派使用手册】第十四节 DAC-输出电压实验
14.1 实验内容 通过本实验主要学习以下内容: DAC工作原理 使用DAC输出电压 14.2 实验原理 14.2.1 DAC工作原理 我们上几章学习了ADC,本章我们来学习DAC。ADC是模-数转换,即模拟量转换为数字量,DAC正好相反,即数-…...
java 使用Log4j进行日志记录
要在Java项目中使用Log4j进行日志记录,需要经过以下步骤: 添加Log4j依赖:在项目的pom.xml文件中,添加Log4j依赖。例如: <dependency><groupId>log4j</groupId><artifactId>log4j</artifa…...
【问题解决】adb remount 失败或刷机无法连接设备(KaiOS)
问题描述 1、设备无法adb remount成功, 2、通过fastboot无法识别设备,一直卡住 3、已经识别到9008端口,但是设备与刷机工具connect fail,甚至软件crash 解决方案 1、安装高通驱动工具:QDLoder HS-USB Driver QDLoade…...
为什么电容两端电压不能突变
我们先从RC延时电路说起吧,图1是最简单的RC延时电路,给一个阶跃的电压信号,电压会变成黄色曲线这个样子,这是为什么呢? 图1 电压跳变后,电源负极电子移动到电容下极板,排斥上极板电子流动到电源…...
Redux 与 MVI:Android 应用的对比
Redux 与 MVI:Android 应用的对比 在为 Android 应用选择合适的状态管理架构时可能会感到困惑。在这个领域中,有两种流行的选择是 Redux 和 MVI(Model-View-Intent)。两者都有各自的优缺点,因此在深入研究之前了解它们…...
《MySQL是怎样运行的》读书笔记(三) B+树索引
前言 从前面数据存储结构中我们已经知道了页和记录的关系示意图: 其中页a、页b、页c ... 页n 这些页可以不在物理结构上相连,只要通过双向链表相关联即可。 在正式介绍索引之前,我们需要了解一下没有索引的时候是怎么查找记录的。下边先只讨论搜索条件…...
微信小程序基础工作模板
1.轮播图 点击跳转官方文档 简单例子 <!-- 顶部轮播图 --> <swiper indicator-dots"true" class"banner" autoplay"true" interval"2000"><swiper-item><image src"../../images/轮播图1.jpg" >…...
简单说一下STL中的map容器的特点、底层实现和应用场景【面试】
特点: 基于红黑树:std::map利用红黑树的自平衡特性,确保操作的平衡性。有序容器:元素根据键的顺序自动排序,排序依据是预定义的键比较函数。唯一键值:容器保证每个键的唯一性,不允许重复键存在…...
Ubuntu22.04之有道词典无法画词翻译替代方案(二百四十九)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
AnythingLLM 的 Docker 使用
AnythingLLM是使用大语言模型LLM的一站式简便框架。官网的介绍如下: AnythingLLM is the easiest to use, all-in-one AI application that can do RAG, AI Agents, and much more with no code or infrastructure headaches. 1. 使用官方docker 最方便的方法是使…...
数组还可以这样用!常用但不为人知的应用场景
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一…...
C++模板元编程:编译时的魔法
1. 引言 在C的世界中,模板元编程是一种在编译时执行计算的强大技术。它允许开发者编写高度灵活和高效的代码,这些代码可以在不牺牲性能的前提下,根据类型和值的不同而变化。本文将深入探讨模板元编程的奥秘,并展示如何在现代C开发…...
SQL进阶day10————多表查询
目录 1嵌套子查询 1.1月均完成试卷数不小于3的用户爱作答的类别 1.2月均完成试卷数不小于3的用户爱作答的类别 编辑1.3 作答试卷得分大于过80的人的用户等级分布 2合并查询 2.1每个题目和每份试卷被作答的人数和次数 2.2分别满足两个活动的人 3连接查询 3.1满足条件…...
debug调试_以Pycharm为例
文章目录 作用步骤打断点调试调试窗口 作用 主要是检查逻辑错误,而非语法错误。 步骤 打断点 在需要调试的代码行前打断点,执行后会停顿在断点位置(不运行) 调试 右键“debug”,或者直接点击右上角的小虫子 调试…...
wms第三方海外仓系统:如何为中小型海外仓注入新活力
对于中小型海外仓来说,想在大型集团海外仓同台竞争中获得优胜,提升其管理效率是非常关键的一环。 我们所熟知的wms系统,也就是第三方成熟海外仓系统,正是这些海外仓企业提升管理水平、降低成本的重要工具。 1、wms第三方海外仓系…...
html是什么?http是什么?
html Html是什么?http是什么? Html 超文本标记语言;负责网页的架构; http((HyperText Transfer Protocol)超文本传输协议; https(全称:Hypertext Transfer Protocol …...
L1-007 念数字js实现
异步解法 const readline require("readline"); const rl readline.createInterface({input: process.stdin,output: process.stdout, }); const input_arr [];//储存数据 rl.on(line, function (line) {input_arr.push(line); } ); rl.on(close, function () {/…...
Perl 运算符
Perl 运算符 Perl 是一种功能强大的编程语言,广泛应用于系统管理、网络编程、GUI 创建、数据库访问等众多领域。Perl 的语法灵活,支持多种编程范式,包括过程式、面向对象和函数式编程。在 Perl 中,运算符扮演着重要的角色&#x…...
深圳市做网站公司/怎么做网站宣传
关于深度学习的框架之争一直都没停止过,每隔一阵大家就要进行一次框架大讨论: TensorFlow的使用者虽多,又有谷歌的背书,但真的很!难!用! Pytorch虽然入门简单、上手快,但因为开源时…...
如何查找网站死链/网络广告有哪些形式
点击上方篮字“X射线衍射和物相分析”可订阅哦!大家在使用衍射仪收集完成倒易空间图数据以后,衍射图用到文章里时都需要二次作图,而仪器原生软件做出的图均无法编辑,不利于文章的发表。这里我演示如何将实验的csv数据利用OriginLa…...
影院网站如何做/杭州seo网络公司
服务容错和雪崩效应 现有架构总结 Consul->服务发现与配置管理 Ribbon->负载均衡 Feign->代码优雅 本章目标 高并发 -》 服务容错? 雪崩效应 比如存在C->B->A三个微服务,某个时刻微服务A在高并发场景下崩溃,那么微服务…...
万维网 网站到期/seo推广培训中心
在输入正确的账号密码后,单击确定后,登陆界面关闭,弹出一个新窗体。 首先定义Login类,并定义创建窗体的方法 需要注意的是,JFrame、JLabel、JTextField、JPasswordField、JButton都是javax.swing包下的类(FlowLayout是…...
温州平阳县营销型网站建设/链接交易网
今天写第一篇在博客园的博文。马上要期末考试了,我却一点也不想复习,最近的烦心事有点多。形式与政策课还有一次讲座没听,却被告知学校不会再安排了,真他妈倒霉。加上前几次讲座笔记记的是一塌糊涂,别给我挂了就行。 前…...
网站怎么做备案号超链接/seo广州工作好吗
State的定义:不同的状态,不同的行为;或者说,每个状态有着相应的行为。何时使用状态模式State模式在实际使用中比较多,适合"状态的切换"。因为我们经常会使用If elseif else 进行状态切换, 如果针…...