【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++继承并非一蹴而就的过程。它需要我们不断地实践、思考、总结和创新。在未来的编程之路上,我们将继续深化对继承的理解,探索其更多的应用场景和高级特性,如虚继承、接口继承等,我们也要认识到继承并非万能的。在使用继承时,我们需要权衡其带来的好处和潜在的风险,避免过度使用导致代码结构复杂、难以维护。我们应该根据具体的需求和场景,选择最合适的编程范式和工具!!!
在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1m2qp8pe3h4nz

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

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

相关文章

MPLS提高网络服务质量的原理

MPLS&#xff08;Multiprotocol Label Switching&#xff0c;多协议标签交换&#xff09;是一种网络技术&#xff0c;它能够提高网络的服务质量&#xff08;Quality of Service&#xff0c;QoS&#xff09;以及整体性能。MPLS通过以下几种方式来提升网络服务质量&#xff1a;标…

220v转3v用多大电阻

在设计一个电压转换电路&#xff0c;将220V交流&#xff08;AC&#xff09;电压转换为3V直流&#xff08;DC&#xff09;电压时&#xff0c;我们需要考虑几个关键因素&#xff0c;包括安全、效率和电路的稳定性。AH8651是一款DC-DC转换器&#xff0c;通常用于将较高的输入电压转…

如何基于 Python 快速搭建 QQ 开放平台 QQ 群官方机器人详细教程(更新中)

注册 QQ 开放平台账号 账号注册 QQ 机器人&#xff1a;一个机器人可以被添加到 群聊/频道 内对话&#xff0c;QQ 用户也可以直接跟机器人 单独对话。 开发者账号主体要求 单聊对话&#xff1a;【定向邀请】 群聊场景&#xff1a;仅支持企业主体【个人主体暂不支持】 频道场…

笔记98:按列压缩矩阵 csc_matrix 的 “含义”

1. 如何按列压缩矩阵&#xff1a; 注&#xff1a;按列压缩&#xff08;Compressed Sparse Column -- CSC&#xff09;&#xff0c;是一种使用三个特征数组就可以表示整个矩阵的方法&#xff1b; 标准二次规划问题 &#xff1a;状态量&#xff1a;矩阵&#xff1a;向量&#xff…

Linux内核驱动入门 编译环境搭建、编译内核

文章目录 前言搭建内核驱动编译环境下载交叉编译工具编译内核minicom工具使用找不到ttyUSB设备问题编译内核编译报错解决小坑编译选项说明 从零开始的驱动程序 前言 哎…有时候我都不知道自己是干啥的 说是运维吧&#xff0c;docker不会&#xff0c;k8s不会&#xff1b;说是驱…

【安卓】在安卓中使用HTTP协议的最佳实践

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

领夹无线麦克风哪个品牌好?分享麦克风什么牌子的音质比较好!

​无线领夹麦克风作为现代音频技术的杰出代表&#xff0c;正逐渐改变着我们的声音表达方式。它以其独特的便携性、稳定性和高音质&#xff0c;成为了众多声音创作者的首选工具。面对市场上琳琅满目的无线领夹麦克风选项&#xff0c;找到一款贴合个人需求的设备无疑是一项挑战。…

万字长文爆肝Spring(一)

Spring_day01 今日目标 掌握Spring相关概念完成IOC/DI的入门案例编写掌握IOC的相关配置与使用掌握DI的相关配置与使用 1&#xff0c;课程介绍 对于一门新技术&#xff0c;我们需要从为什么要学、学什么以及怎么学这三个方向入手来学习。那对于Spring来说: 1.1 为什么要学? …

金融科技助力绿色金融:可持续发展新动力

随着全球气候变化和环境问题的日益严重&#xff0c;绿色金融作为推动环境保护和经济可持续发展的重要手段&#xff0c;已经受到越来越多的关注。而金融科技&#xff0c;作为科技与金融深度融合的产物&#xff0c;正以其独特的优势为绿色金融的发展注入新动力。本文将探讨金融科…

Vue 路由:一级路由,嵌套路由

1、安装路由插件,因为用的是vue2 所以路由版本要和vue2对应上&#xff0c;所有有3 yarn add vue-router3 2、在main.js里引入 import VueRouter from vue-router Vue.use(VueRouter) 3、新建文件夹 router,创建index.js 4、引入路由插件&#xff0c;并且暴露出来这个路由 5、在…

基于机器学习的C-MAPSS涡扇发动机RUL预测

美国国家航空航天局的商用模块化航空推进仿真系统&#xff08;CMAPSS&#xff09;所模拟出的涡扇发动机性能退化数据进行实验验证&#xff0c;数据中包含有风扇、涡轮、压气机等组件参数。C-MAPSS中所包含的数据集可以模拟出从海平面到42千英尺的高度&#xff0c;从0到0.9马赫的…

一键实现电脑投屏到电视机,轻松享受更大画面

在数字化的今天&#xff0c;我们常常希望在更大的屏幕上分享电脑上的内容&#xff0c;观看视频、展示演示文稿&#xff0c;或者与家人一同欣赏照片。而实现电脑屏幕投射到电视机上&#xff0c;成为了许多人追求的方便而实用的功能。本文将为您详细介绍电脑投屏到电视机的方法&a…

汽车IVI中控开发入门及进阶(二十六):视频解码芯片ADV7180

前言: ADV7180芯片的功能框图如下: ADV7180自动检测并将兼容全球NTSC、PAL和SECAM标准的标准模拟基带电视信号转换为兼容8位ITU-R BT.656接口标准的4:2:2分量视频数据。简单的数字输出接口与各种MPEG编码器、编解码器、移动视频处理器以及Analog Devices数字视频编码器(如A…

跨平台看抖音、哔哩哔哩、虎牙、斗鱼啦,一个app即可完成

一、简介 1、一款免费、开源、无广告、跨平台的,可以观看抖音、哔哩哔哩、虎牙、斗鱼等平台的直播内容的软件。它简单好用,支持 Windows、MacOS、Linux、Android、iOS 等平台。 二、下载 1、文末有下载链接,apk手机可直接安装,不明白可以私聊我哈(麻烦咚咚咚,动动小手给个…

Matrix->Matrix工具类获取Matrix的平移、缩放、错切数值

// 传入矩阵&#xff0c;获取矩阵数值 class MatrixValues(matrix: Matrix) {val scaleX: Floatval scaleY: Floatval transX: Floatval transY: Floatval skewX : Float val skewY : Floatinit {val fromValues FloatArray(9)matrix.getValues(fromValues)// 缩放数值scaleX …

有什么好用的ai智能写作手机版?6个软件帮助你快速进行智能写作

有什么好用的ai智能写作手机版&#xff1f;6个软件帮助你快速进行智能写作 AI智能写作在现代社会中扮演着越来越重要的角色&#xff0c;许多人依赖这些工具来提高写作效率和质量。以下是六款不同类型的AI智能写作手机应用&#xff0c;它们可以帮助你快速进行智能写作&#xff…

element 表格el-table的 :cell-style用法-表格固定行文字高亮

el-table的 :cell-style用法 实现表格固定行文字高亮效果 <el-tableref"table"borderstripe:data"list":height"height"highlight-current-row:cell-style"cellStyle"><el-table-columnprop"code"label"规则…

2024年【陕西省安全员C证】考试资料及陕西省安全员C证考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 陕西省安全员C证考试资料根据新陕西省安全员C证考试大纲要求&#xff0c;安全生产模拟考试一点通将陕西省安全员C证模拟考试试题进行汇编&#xff0c;组成一套陕西省安全员C证全真模拟考试试题&#xff0c;学员可通过…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 团队派遣(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍓OJ题目截图 📎在线评测链接 团队派遣(100分) 🌍 评测功能需要订阅专栏…

Python第二语言(十一、Python面向对象(下))

目录 1. 封装 1.1 私有成员&#xff1a;__成员、__成员方法 2. 继承&#xff1a;单继承、多继承 2.1 继承的基础语法 2.2 复写 & 子类使用父类成员 3. 变量的类型注解&#xff1a;给变量标识变量类型 3.1 为什么需要类型注解 3.2 类型注解 3.3 类型注解的语法 3.…