继承杂谈。

在这里插入图片描述

内容一览

      • 前言
      • 继承的概念及定义
      • 继承的意义
      • 继承关系及访问限定符
      • 父类和子类对象之间的转化
      • 继承后的作用域
      • 继承与有元
      • 继承与静态成员
      • 多继承
      • 继承和组合的区别:
      • 继承的总结和反思

前言

 面向对象的三大特性:封装继承和多态,这三种特性优者很紧密地联系,但是也有很大的区别。
 对于刚接触面度对象编程风格的小白,你可能不了解继承到底是用来做什么的,以及为什么叫做继承,接下来我就带你仔细了解继承的作用以及注意事项。

继承的概念及定义

 继承是代码可以复用的重要手段,术语表达继承就是子类继承父类的属性和方法,大白话就是子承父业,子类可以拥有父类除了私有以外的成员和方法,还可以有自己的属性,父类通常也叫做基类,子类有时也叫做派生类。
 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,在之前我们在程序中的复用通常是函数复用,传入不同的参数,函数就可以产生不同的响应,但是在学完继承和多态后,代码的复用就不仅仅局限于函数的复用,继承就是类设计层次的复用。
举一个例子理解一下

class animal
{
public:void Paint(){cout << age << endl;}
protected:int age=10;
};class cat :public animal
{
protected:int price = 500;
};

 动物是一个较大的概念,在动物的种类中有猫,小狗等等,他们具有都是动物的特点,也与属于他们自己的特点,继承animal后,cat就是animal的子类,animal就是cat的父类。
在这里插入图片描述
如上图所示,如果用子类构造出一个对象,子类也包含了父类的成员和方法。
此时,用父类函数构造出来的子类就也可以使用父类的方法。
在这里插入图片描述

继承的意义

 现在我们要思考了,为什么我们需要继承呢?向上变得例子来看,继承并没有带来太大的优化,这样你就大错特错了,如果仅仅只有两三个类,在每个类的属性和方法都确定的请款下确实某有必要实现继承,但是在现实中通常是一整个系统程序,往往有很多类且大部分类都是具有相似部分的,如果我们每个类都丹毒重新写,不仅代码臃肿而且工作量大,出bug不易查寻。
 有了继承之后,我们可以将部分类中相似的部分提取出来作为父类,在继承父类后添加自己新的属性和方法,这样不仅可以大大减少代码量,还易于维护,代码结构清晰可见。

继承关系及访问限定符

 C++中有三种访问限定符,分别是public,private,protected,继承方式也就有了三种,最常用的就是public继承。
在这里插入图片描述
如上图所示
私密性public<protected<private。
 刚开始时是没有保护这个限定符的,正式出现继承和多态之后才加上的,父类的私有子类不能访问,但父类的保护子类也可以访问。

父类和子类对象之间的转化

子类的对象可以赋值给父类的对象/父类的指针/父类的引用,这里有一种很形象的说法就是切片或者切割,子类的对象继承了父类,所以包含父类的一部分,在赋值给父类对象时,将子类父类的一部分切割给父类对象。
切记!只有子类对象可以赋值给父类。

	cat c;animal a = c;//子类对象赋值给父类animal* a = &c;//赋值子类对象的指针animal& k = c;//赋值子类对象的引用

赋值切片的过程类似于
在这里插入图片描述

继承后的作用域

 继承后子类和父类仍然是两个独立的作用域。所以要注意这个问题,如果子类和父类包含有同名函数,这两个函数之间的关系不是重载(重载要求两个函数在同一作用域),而是隐藏,子类会隐藏父类的同名函数。
 要注意哈!在父子类中,只要两函数的名称相同就构成隐藏。但是在实际中最好不要在父子类中定义同名函数,不然会容易混淆。
 如果构成隐藏后,我们调用该函数就调用的是子类的虚函数,如果我们想要调用父类的虚函数,就要加上作用域(在子类成员函数中,可以使用基类::基类成员,显式访问)。

class person
{
public:void func1(){cout << " person::func1" << endl;}
};
class student : public person
{
public:void func1(){cout << "func1" << endl;}
};int main()
{person p;student s;p.func1();s.func1();return 0;
}

在这里插入图片描述
 运行后发现,父类调用该函数使用的父类中的,子类调用就调用子类中的函数,子类将父类的同名函数进行隐藏,这样搞调用时就不会产生歧义。
接下来探索探索继承后,子类的成员函数和弗雷德成员函数之间的关系。

  • 1,首先,子类的构造函数必须先调用父类的构造函数初始化子类中所包含父类的一部分,如果父类中没有默认的构造函数,就必须在子类构造函数的初始化列表进行初始化。
  • 2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  • 5. 派生类对象初始化先调用基类构造再调派生类构造。
  • 6. 派生类对象析构清理先调用派生类析构再调基类的析构。

通过调试来带领大家观察构造及西狗屎的顺序。
在这里插入图片描述
 可以发现,在构造子类对象时,再初始化列表首先调用父类的构造函数,在析构时,首先调用子类的析构函数,然后再调用父类的析构函数。
在这里插入图片描述

继承与有元

 我们知道一个类的有元可以在类外使用类中的变量和方法,那么父类的友元函数能被继承吗?
 答案是不能,友元关系不能被继承,基类的有缘不能访问子类的私有和保护。
举一个例子

class A
{
public:
private:int _a=2;
};
class B:public A
{
public:int _b = 0;friend void Printf();
};
void Printf(B b,A a)
{cout << b._b;cout << a.a;
}
int main()
{A a;B b;Printf(b,a);return 0;
}

 在子类中创建一个友元函数,友元函数只可以访问,而不是父类的友元函数,所以父类中的保护和私有我们这个友元函数不能访问。
在这里插入图片描述

继承与静态成员

 基类定义了static静态成员,整个继承体系里面只有一个这样的成员,无论有多少个子类,静态成员就只有这一个。

class A
{
public:int _a = 2;static int aa;
};
int A::aa=1;
class B :public A
{
public:int _b = 0;
};
int main()
{A a;B b;A::aa++;cout<<a.aa<<endl;a.aa++;cout << b.aa;return 0;
}

 由于两个类中的静态变量只有一份,所以我们可以直接用类加作用域直接访问,也可以用对象进行访问。
如图
在这里插入图片描述

多继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
例如:
在这里插入图片描述

多继承:一个子类同时继承多个父类,这种关系称为多继承
在这里插入图片描述
有了多继承就会产生一种特殊的情况,那就是菱形继承
在这里插入图片描述
这种情况就会产生特殊的影响
 假如A中有一个元素,那么BC分别继承了A,然后D再次继承BC,D中就包含了两份这个元素。如果我们去调用D中A的元素(假设为_a)就会出现二义性。
在这里插入图片描述
 不仅如此,我们D类中包含了两份_a,还会浪费内存。我们可以使用作用域去看。
在这里插入图片描述
如何解决数据重复问题呢?
我们可以使用关键字virtual。
使用虚拟继承就可以解决问题
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
关键字virtual在多态时也会用到,在这里是修饰继承了最上边的父类的子类。在上边的例子中就是修饰B和C。
在这里插入图片描述
正如上图,也是菱形继承的一种,我们要在距离被继承两次的父类的下一层子类加virtual,使其变成虚拟继承。
但是还是那句话,我们在实际写程序中尽量不要写菱形继承。
如下图
在这里插入图片描述
此时再来观察在内存中是如何解决这件问题的。
借用上边的例子
在这里插入图片描述
我们来调试运行看一看。
在这里插入图片描述
 可以发现,不管我们调用B还是C对象中的_a元素,都是调用的同一个,来看一看底层如何实现的,以及为什么将A类中的_a放在最后的位置。
我们在X86的环境下来观察
在这里插入图片描述
 可以发现,在B和C对象前都有一个指针,这个指针里存的就是该位置到_a的距离,当我们想要修改A中_a的值时,就会找到该位置距离A对象中_a的长度,然后修改A中_a的值。
 将A中数据放在末尾就是为了统一管理,方便每一个继承该父类的子类通过指针找到该位置。

继承和组合的区别:

public是一种is-a的关系,也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系,假设B组合了A,每个B对象中都会有一个A对象。
在这里插入图片描述
优先使用对象组合,而不是类继承。
 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。
 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。
 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。

继承的总结和反思

 继承大大增加了我们代码的复用性,有人说C++语法复杂,其实就是多继承的问题,比建议设计出多继承,java就吸取了C++的教训,没有引入多继承,如果使用多继承不小心设计出了菱形继承,那会在复杂度和时间上都产生问题。
本文结束,如果有问题或者有疑问就直接评论哦,回复很快。

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

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

相关文章

Flink实时数仓同步:实时表实战详解

一、背景 在大数据领域&#xff0c;初始阶段业务数据通常被存储于关系型数据库&#xff0c;如MySQL。然而&#xff0c;为满足日常分析和报表等需求&#xff0c;大数据平台采用多种同步方式&#xff0c;以适应这些业务数据的不同存储需求。这些同步存储方式包括离线仓库和实时仓…

VUE3 使用axios网络请求

1.新建工程 参考&#xff0c;VUE3 环境搭建&#xff1a;https://blog.csdn.net/LQ_001/article/details/136293795&#xff0c;运行命令 vue create vue-demo 2.引入axios 不管何种引用&#xff0c;都要在工程中安装 axios 包。安装命令&#xff1a;npm install --save axio…

python的scripts文件夹作用

Windows系统&#xff1a; Scripts文件夹通常位于Python的安装目录下&#xff0c;如C:\Python\Scripts。该文件夹内包含了各种有用的工具&#xff0c;例如pip、virtualenv等&#xff0c;这些工具有助于管理和配置Python环境和依赖包。 Linux系统&#xff1a; 在Linux系统中&…

flink重温笔记(十四): flink 高级特性和新特性(3)——数据类型及 Avro 序列化

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 14 天啦&#xff01;学习了 flink 高级特性和新特性之数据类型及 avro 序列化&#xff0c;主要是解决大数据领域数据规范化写入和规范化读取的问题&#xff0c;avro 数据结构可以节约存储空间&#xff0c;本文中结合企业真…

[BJDCTF2020]----EzPHP

文章目录 pass-1pass-2pass-3pass-4pass-5pass-6pass-7 查看题目&#xff0c;右键源代码&#xff0c;发现GFXEIM3YFZYGQ4A&#xff0c;base64解码&#xff1a;1nD3x.php 访问1nD3x.php&#xff0c;代码审计&#xff0c;一步一步分析 <?php highlight_file(__FILE__); error…

C++的类与对象(三):构造函数、析构函数、对象的销毁顺序

目录 类的6个默认成员函数 构造函数 语法 特性 析构函数 特性 对象的销毁顺序​​​​​​​​​​​​​​ 类的6个默认成员函数 问题&#xff1a;一个什么成员都没的类叫做空类&#xff0c;空类中真的什么都没有吗&#xff1f; 基本概念&#xff1a;任何类在什么都不…

基于范围的for循环(C++11)和auto

auto C11中&#xff0c;标准委员会赋予了auto全新的含义即&#xff1a; auto不再是一个存储类型指示符&#xff0c;而是作为一个新的类型 指示符来指示编译器&#xff0c;auto声明的变量必须由编译器在编译时期推导而得。 int a 10;auto b a;auto c a;auto d TestAuto(…

第三周组会——动态多目标优化算法

首先对上周写的DF测试函数进行了优化和增加 DF4 pf: DF5测试函数PF DF6 遇到的问题,在算法问题的参数taut(变化频率)默认是10数字变小时就算是9,算法会跟不上收敛 新读的文献 A Novel Dynamic Multiobjective Optimization Algorithm With Hierarchical Response System 一…

基于Vue的娱讯移动端APP前端设计与实现

目 录 摘 要 Abstract 引 言 1绪论 1.1课题背景及目的 1.1.1移动端APP发展简介 3 1.1.2移动端APP的优势 3 1.2前端开发相关技术 1.2.1前端开发工具介绍 3 1.2.2 前端开发相关技术介绍 4 1.3本章小结 2系统分析 2.1功能需求分析 2.2系统工作流程 2.3本章小结 3系统设…

JDK 17:Java生态系统的最新巨擘

JDK 17&#xff1a;Java生态系统的最新巨擘 &#x1f680; JDK 17&#xff1a;Java生态系统的最新巨擘 &#x1f680;摘要 &#x1f31f;引言 &#x1f308;模块一&#xff1a;性能优化与提升 &#x1f527;垃圾回收器的改进&#xff1a;JIT编译器的优化&#xff1a;其他性能优…

基于河马优化算法(Hippopotamus optimization algorithm,HO)的无人机三维路径规划

一、无人机路径规划模型介绍 二、算法介绍 河马优化算法&#xff08;Hippopotamus optimization algorithm&#xff0c;HO&#xff09;由Amiri等人于2024年提出&#xff0c;该算法模拟了河马在河流或池塘中的位置更新、针对捕食者的防御策略以及规避方法。2024最新算法&#x…

java中移位<< >> <<< |数据类型转换

移位 x64转换二进制&#xff1a;100 0000 左移2位 &#xff1a; 1000 0000 0 对应十进制 i 256 >>右移 <<左移 >>无符号位右移 关于右移一位相当于整除2 数据类型及其转换 基本数据类型&#xff0c;数据类型范围 byte(-128~127)&#xff08;-2^7~2…