【C++技能树】继承概念与解析

在这里插入图片描述
Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!

继承

  • 0. 继承概念
    • 0.1 继承访问限定符
  • 1. 基类和派生类对象赋值兼容转换
  • 2. 继承中的作用域
  • 3. 派生类中的默认成员函数
  • 4.友元
  • 5.继承中的静态成员
  • 6.菱形继承
  • 7.菱形虚拟继承
  • 总结
  • 总结

在这里插入图片描述

0. 继承概念

​ 设想一个场景,你需要设计学生、老师、教授…的类,除了每个身份中独有的信息,例如:学号,工号,教授身份号,但是他们都有一个共同的属性,就是人.所以我们可以先设计一个类:人.

每设计一个新的类都可以复用人这个类,增加了代码的复用性.这就是C++中的新特性:继承

我们之前接触的函数重载是函数层面的复用,继承则是类层面的复用

class Person{
public:void print(){cout<<age<<" "<<name<<endl;}int age=0;string name="Peter";string address;int tel;
};
class Student:public Person
{
public:int _stuid;
};

我们可以通过调用来看看其结构模型.

int main()
{Person p1;cout<<p1.name<<endl;Student s1;cout << s1._stuid;
}

image-20230823184212791

子类中可以共享父类中的变量,父类不可以访问子类的变量.

0.1 继承访问限定符

在类中有访问限定符,同样的,在继承方式上也有访问限定符.

24756bb84f210a146239f27a1e1494b

派生类(子类)可以通过以下方式来继承基类(父类):

image-20230823193458458

在这里新出现了一个权限符号,protected.它与private是类似的:

protected修饰的变量在类外与private类似,不能被访问.

但是在派生类中可以访问protected修饰的变量,而不能访问private修饰的变量

所以权限的大小的关系为:

public>protected>private

所以,在权限的继承中有一个最小原则.

  1. 以public来继承,可以继承的变量为:public,protected
  2. 以protected来继承.可以继承的变量为:protected
  3. 以private来继承,无可以继承的变量

通常情况下,我们一般用public来继承,protected/private的继承方式实用性不高

1. 基类和派生类对象赋值兼容转换

派生类可以转换为基类,而基类并不能转换为派生类.

例如上面Person与Student的例子:

可以实现
Person p1;
Student s1;
p1=s1;
不能实现
s1=p1;

这其实很好理解.父类中的属性往往比子类中的成员多,子类中的成员可以通过切割多余的成员转换到父类中.

通过这样的方式,这中间不涉及强制类型转换.我们可以通过以下这个例子来看.

我们知道,强制类型转换会产生一个临时变量.例如:

int a=10;
double b=a;

这当中会产生一个临时变量double a,来赋值给b.

Person &p1=s1;

如果产生了临时变量,这个赋值是不可以的.但通过编译器验证,我们发现这样是可以的.侧面的说明这并不是引用.

但对于p1的成员进行修改,s1也会同样被修改.

Student s1;
Person p1 = s1;
p1.name = "H";

Before:

image-20230823231534645

After:

image-20230823231912045

虽然这种限制(子类可以转父类,父类不可转子类)可以通过指针直接访问内存的方法解除

原来指向的是student对象,现在强制转换为student指针是可以的.

Student s1;
Person *p1 = &s1;
Student* sp1 = (Student*)p1;
sp1->age = 10;

原来指向的是person对象,现在强制转换为student指针则会发生越界

Student s1;
Person pp1;
Person *pp1 = &s1;
Student* sp1 = (Student*)pp1;
sp1->age = 10;

2. 继承中的作用域

一个{}是一个作用域,所以在基类和派生类中,都有自己的作用域.

所以当在派生类中定义与基类相同的名的变量的时候就会构成隐藏:隐藏父类的相关变量

当在派生类中定义与基类相同的函数时(只需要名字相同),就会构成重定义(隐藏):重定义父类相关函数

如果需要访问父类被隐藏的属性,需要在前加类域限定符才能访问

#include<iostream>
using namespace std;
class Person {
public:void print(){cout <<"Person:" << age << endl;}int age = 0;
};
class Student :public Person
{
public:void print(int i){cout <<"Student:" << age << endl;}int age=10;};
int main()
{Student s1;cout << s1.age; //10s1.print(1);// student:10cout << s1.Person::age;//0s1.Person::print();//person:0return 0;
}

3. 派生类中的默认成员函数

总的来说,派生类中的所有涉及父类的行为都要从父类当中去寻找相关方法论:

  1. 派生类初始化的时候会先调用父类的初始化函数,在调用自己的.若父类没有默认构造函数则需要在派生类中的初始化列表中调用父类构造函数传入参数.(**为什么需要在初始化列表中调用不在函数体里调用呢?**自定义类型成员(且该类没有默认构造函数时)在初始化列表中调用相关文章:初始化列表

    class Person{
    public:Person(int sage):age(sage){}void print(){cout<<age<<" "<<name<<endl;}int age=0;string name="Peter";string address;int tel;
    private:int s=0;
    };
    class Student:public Person
    {
    public:Student(int _age):Person(_age){}int _stuid;
    };
    
  2. 拷贝构造与赋值运算符重载需要通过显式调用父类中的方法来完成.

    class person
    {
    public:person(const char *name="peter"):_name(name){cout<<"person()";}person(const person&p1):_name(p1._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;}string _name;};
    class student:public person
    {
    public:student(const char*name="zhangsan",int id=0):person(name),_id(id){cout<<"student()"<<endl;}student(const student&s1):person(s1),_id(s1._id){}student& operator=(const student& s1){if(this!=&s1){//出现隐藏,想要调用父类的=person::operator=(s1);_id=s1._id;}return *this;}void print(){cout<<_id<<" "<<_name<<endl;}
    private:int _id;
    };
    
  3. 析构函数不需要显式调用父类(也不能),编译器会自己调用完派生类的析构函数,在调用基类的析构函数

    其实这也很好理解.从函数栈帧方面:先创建父类再创建子类,自然先析构子类再析构父类.

    从内存保护方面:在子类中有可能调用了父类的成员对象,如果先消除父类,会导致子类中出现野指针的情况

16375783a643a74a0431af96eefa36c

4.友元

父类的友元不能访问子类的成员变量。(父亲的朋友不是孩子的朋友

class B;
class A{
friend void print(const A& a1,const B& b1);
private:int a=10;};
class B:public A{
private:int b=100;
};
void print(const A& a1,const B&b1)
{cout<<a1.a<<endl; //rightcout<<b1.a<<endl; //rightcout<<b1.b<<endl; //error
}

在上面的例子中可以看到:print函数可以访问A的private,而不能访问B中的private

5.继承中的静态成员

静态成员只会存在一份.在父类当中,子类中可以继承静态成员.但是继承的是访问权,只能访问不能修改

且其是存在类当中,也就是无论几个对象,访问的都是同一个静态成员

class B;
class A{
friend void print(const A& a1,const B& b1);
public:static int count;
private:int a=10;
};
int A::count=10;
class B:public A{
public:void print(){cout<<count<<" "<<endl;}private:int b=100;};void print(const A& a1,const B&b1)
{cout<<a1.count<<endl;cout<<b1.count<<endl;cout<<A::count;
}int main()
{A a1;B b1;print(a1, b1);//10A::count++;print(a1, b1);//11}

6.菱形继承

在c++中,多继承的结构模型是这样的,使用不当时会导致出现菱形继承的情况.导致内存中会重复出现一些变量.也会导致二义性

cdd3d5768cf48df625009ef25537898

例如,在person中有一个表示年龄的age,在student与teacher中各有表示年龄的age,当professor继承student与teacher时,就会有两个age.这在现实环境中显然是不合理的

image-20230824171105761

#include<iostream>
using namespace std;
class Person {
public:int age = 10;
};class Student :public Person
{
public:int stuid = 1;};
class Teacher :public Person
{int teaid = 2;
};
class Professor:public Student,public Teacher
{int profeid = 3;
};
int main()
{Professor p1;p1.Student::age = 100;p1.Teacher::age = 200;}

其在内存中的模型为:

94c65b2a5cf3f4035fcc7ea7cd1efe6

可以看到此时出现数据冗余二义性.

C++解决这个问题的方法则是:菱形虚拟继承

7.菱形虚拟继承

一个新的关键字:virtual,在之后用到很多,但每个地方的含义都不大相同.

在继承方面,我们用virtual来修饰基类.也就是在上方结构模型的腰部

class Person {
public:int age = 10;
};class Student :virtual public Person
{
public:int stuid = 1;};
class Teacher :virtual public Person
{int teaid = 2;
};
class Professor:public Student,public Teacher
{int profeid = 3;
};
int main()
{Professor p1;p1.Student::age = 100;p1.Teacher::age = 200;cout<<sizeof(p1);
}

此时的内存模型为

b991e55de33001344c16e25553e8e45

结构模型为:

image-20230824174055432

我们发现,重复出现的变量age修饰完只出现了一个.

观察内存模型,我们发现原来存age的地方,变成了一个指针.而age被放在了整个对象的最后一个位置.

f65d42e29d4e453b35e1d5ff4da514c

打开内存模型,我们发现,这个指针指向了一块内存空间.叫虚基表.其中第一个位置为:0(其存放的为虚表偏移量) 第二个位置存着该指针相较于age的偏移量

为什么要这样设计呢:

  1. 解决了数据冗余

  2. 相同的对象可以调用同一个虚基表

  3. 存放偏移量让切割成为了可能

    这里可以这样理解,当我创建了一个teacher的对象,将professor传入,则完成了切割,数据从teacher的指针开始访问,若我此时想要访问a,我直接读取偏移量即可.

总结

继承使C++底层变得复杂了起来,在日常使用中,需要避免出现菱形继承的问题.

更推荐使用组合:在一个类中调用另一个封装完的类,此时被调用的类的细节对调用类来说是不可见的.

相较于继承,更推荐使用组合的方式.高内聚低耦合一直是我们设计程序的原则

型,我们发现原来存age的地方,变成了一个指针.而age被放在了整个对象的最后一个位置.

[外链图片转存中…(img-WM0k8lWU-1692871122130)]

打开内存模型,我们发现,这个指针指向了一块内存空间.叫虚基表.其中第一个位置为:0(其存放的为虚表偏移量) 第二个位置存着该指针相较于age的偏移量

为什么要这样设计呢:

  1. 解决了数据冗余

  2. 相同的对象可以调用同一个虚基表

  3. 存放偏移量让切割成为了可能

    这里可以这样理解,当我创建了一个teacher的对象,将professor传入,则完成了切割,数据从teacher的指针开始访问,若我此时想要访问a,我直接读取偏移量即可.

总结

继承使C++底层变得复杂了起来,在日常使用中,需要避免出现菱形继承的问题.

更推荐使用组合:在一个类中调用另一个封装完的类,此时被调用的类的细节对调用类来说是不可见的.

相较于继承,更推荐使用组合的方式.高内聚低耦合一直是我们设计程序的原则

相关文章:优先使用对象组合,而不是类继承

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

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

相关文章

如何飞速成为开源贡献者(Contributor)

如何飞速成为开源贡献者Contributor 一、环境信息1.1 硬件信息1.2 软件信息 二、Git安装2.1 Git介绍2.2 Git下载安装 三、开源项目选定四、GitHub参与开源流程4.1 Fork项目4.2 SSH配置4.2.1 为什么要配置SSH4.2.2 如何配置SSH 4.3 Clone项目4.4 IDEA关联4.5 PR生成4.6 PR提交 一…

STM32f103入门(9)编码器接口测速

TIM3 PA6 PA7 上拉输入 原理上也是PWM捕获输入 捕获两个输入 我们用中断处理读取CNT的值 读取完将CNT置0 这样我们就得到了旋转编码器的速度/s 中断配置代码 #include "stm32f10x.h" // Device headervoid Timer_Init(void) {RCC_APB1PeriphClockC…

SWAT-MODFLOW地表水与地下水耦合

耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果&#xff0c;SWAT作为一个地表水模型可以较好的模拟主要的水文过程&#xff0c;包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等&#xff0c;但是对于地下水部分的模拟相对粗糙&#xff0c;考虑到SWAT…

com.google.guava:guava 组件安全漏洞及健康分析

组件简介 维护者google组织许可证类型Apache-2.0首次发布2010 年 4 月 26 日最新发布时间2023 年 8 月 1 日GitHub Star48189GitHub Fork10716依赖包28,694依赖存储库219,576 Guava 是 Google 的一组核心 Java 库&#xff0c;其中包括新的集合类型&#xff08;例如 multimap 和…

Web安全——穷举爆破上篇(仅供学习)

Web安全 一、概述二、常见的服务1、burpsuite 穷举后台密码2、burpsuite 对 webshell 穷举破解密码3、有 token 防御的网站后台穷举破解密码3.1 burpsuite 设置宏获取 token 对网站后台密码破解3.2 编写脚本获取token 对网站后台密码破解 4、针对有验证码后台的穷举方法4.1 coo…

ElasticSearch安装为Win11服务

在windows的环境下操作是Elasticsearch,并且喜欢使用命令行 &#xff0c;启动时通过cmd直接在elasticsearch的bin目录下执行elasticsearch ,这样直接启动的话集群名称会默elasticsearch&#xff0c;节点名称会随机生成。 停止就直接在cmd界面按CtrlC 其实我们也可以将elasticse…

一篇文章教会你什么是二叉搜索树

二叉搜索树 二叉搜索树概念二叉搜索树操作1.二叉搜索树的查找2.二叉搜索树的插入3.二叉搜索树的删除4.二叉搜索树的遍历 二叉搜索树的实现1.二叉搜索树节点结构2.二叉搜索树类3.二叉搜索树的构造及析构4.二叉搜索树的拷贝构造及赋值重载5.二叉搜索树插入6.二叉搜索树查找7.二叉…

必读干货!独立站新手卖家常见十大问题解答

新手卖家入局跨境电商独立站&#xff0c;会考虑到很多问题&#xff0c;从网站到产品再到售卖最终的发货。而且卖家普遍对独立站存在认知不全的问题&#xff0c;门槛高、推广难、转化难是最初印象。 那独立站该怎么开始&#xff0c;如何下手&#xff1f;今天整理并解答独立站新…

数据结构--树4.2.2(二叉树--遍历)

目录 一、二叉树的建立 二、二叉树的遍历算法 一、二叉树的建立 CreateBitree(Bitree *t){char c;scanf("%c",&c);if( c){*t NULL;}else{*t(Bitnode*)malloc(sizeof(Bitnode));(*t)->data c;CreateBitree(&(*t)->lchild);CreateBitree(&(*t)-&…

Mybatis学习|注解开发、lombok

1.使用注解开发 无需再编写相应的Mapper.xml文件&#xff0c;直接将sql用注解的形式写在Mapper接口的对应方法上即可。 然后因为没有xml文件,所以要在mybatis-config.xml核心配置文件中注册这个Mapper接口&#xff0c;而不用去注册之前的Mapper.xml&#xff0c;这里其实如果用…

iOS 设置下载部分文件,如何获取完整文件的大小

在视频的需求中&#xff0c;遇到这样一个需求&#xff0c;播放一视频的时候&#xff0c;要预下载 后面10条视频&#xff0c;但是只下载后面十条视频的前面1M 实现方法 1 创建请求时设置cacheLength resource [[IdiotResource alloc] init];resource.requestURL task.request…

UE5.1 透明渲染流程框架图

相关文章&#xff1a; UE 透明物体绘制准备_sh15285118586的博客-CSDN博客 透明直接光和间接光生成_sh15285118586的博客-CSDN博客 Scene:Translucency-Translucency(AfterDOF)_sh15285118586的博客-CSDN博客 Scene:Translucency-Distortion &PostProcessing:ComposeTran…

详解vue3中ref和reactive用法和区别

vue3中ref和reactive区别 1、前言2、基本用法2.1 ref2.2 reactive 3、ref和reactive定义数组对比3.1 ref定义数组3.1 reactive定义数组 4、ref 和reactive的区别 1、前言 ref和reactive是Vue3中用来实现数据响应式的API&#xff0c;一般情况下&#xff0c;ref定义基本数据类型…

react css 污染解决方法

上代码 .m-nav-bar {background: #171a21;.content {height: 104px;margin: 0px auto;} }import React from "react"; import styles from ./css.module.scssexport default class NavBar extends React.Component<any, any> {constructor (props: any) {supe…

定位与轨迹-百度鹰眼轨迹开放平台-学习笔记

1. 百度鹰眼轨迹的主要功能接口 百度的鹰眼轨迹平台&#xff0c;根据使用场景不同&#xff0c;提供了web端、安卓端等各种类型的API与SDK&#xff0c;本文章以web端API为例&#xff0c;介绍鹰眼轨迹的使用。 2. API使用前的准备 使用鹰眼轨迹API&#xff0c;需要两把钥匙&…

分布式session的4种解决方案

分布式session的4种解决方案 1、cookie和session cookie和session都是用来跟踪用户身份信息的会话方式。 cookie存储的数据保存在本地客户端&#xff0c;用户获取容易&#xff0c;但安全性不高&#xff0c;存储数据小。 session存储的数据保存在服务器&#xff0c;用户不易获取…

Redis网络模型

目录 Redis网络模型 用户空间和内核态空间 阻塞IO(BIO) 非阻塞IO(NIO) IO多路复用 信号驱动IO 异步IO(AIO) Redis到底是单线程还是多线程&#xff1f; 为什么要使用单线程&#xff1f; Redis网络模型 进程的寻址空间会划分为两部分&#xff1a;内核空间、用户空间 用…

自然语言处理(六):词的相似性和类比任务

词的相似性和类比任务 在前面的章节中&#xff0c;我们在一个小的数据集上训练了一个word2vec模型&#xff0c;并使用它为一个输入词寻找语义相似的词。实际上&#xff0c;在大型语料库上预先训练的词向量可以应用于下游的自然语言处理任务&#xff0c;为了直观地演示大型语料…

2022年下半年系统架构设计师真题(下午带答案)

试题一 (25分) 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管理方式的灵活性&#xff0c;由于当前用户规模不大&#xff0c;业务也相对…

使用 ElasticSearch 作为知识库,存储向量及相似性搜索

一、ElasticSearch 向量存储及相似性搜索 在当今大数据时代&#xff0c;快速有效地搜索和分析海量数据成为了许多企业和组织的重要需求。Elasticsearch 作为一款功能强大的分布式搜索和分析引擎&#xff0c;为我们提供了一种优秀的解决方案。除了传统的文本搜索&#xff0c;El…