C++类的三大特性之继承

2023-11-14

一:继承的概念与使用

<1> 什么是继承?

  • 通俗的说,继承是类设计层次的复用
  • 我们之前都接触过函数复用,就是在一个函数中调用另一个函数完成其部分或全部功能,比如push_back复用insert
  • 而继承就是有一个类去复用另一个类,我们把被继承的类叫做基类或者父类,继承的类叫做派生类或者子类。
  • 派生类拥有基类的功能的同时,还加上了独属于自己的功能
  • 也许你还不理解为什么要有继承这个东西,举个栗子吧
  • 现在我们需要两个类,一个是学生,一个是教师,他们都有成员变量name,id.但是还有其它的变量或者成员函数,我们定义这两个类时写这些共有的东西就显得冗余,于是我们写个类Person,它只拥有name和id两个成员变量,和Student和Teacher类都有的成员函数,比如Show
class Person
{
public:
    void Show()
    {
        cout << "name: " << _name << endl;
        cout << "id: " << _id << endl;
    }
private:
    string _name;
    string _id;
};
class Student : public Person
{
private:
    double _gpa;
};
class Teacher : public Person
{
private:
    string _title;
};

<2> 如何使用

  • 继承的格式是class Student :public Person,其中Student是派生类,public是继承方式,Person是基类,也就是class 派生类 :继承方式 基类
  • 这里提到了继承方式,不同的继承方式会导致派生类中访问基类成员的方式发生变化
  • 具体如下表:
类成员/ 继承方式 Public继承 Protect继承 private继承
基类的public成员 派生类public 派生类protect 派生类private
基类的protect成员 派生类protect 派生类protect 派生类private
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见
  • 总结:
  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 基类的私有成员在子类都是不可见,基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
class Person
{
public:
    void Show()
    {
        cout << "name: " << _name << endl;
        cout << "id: " << _id << endl;
    }
    string _name="张三";
    string _id="10086";
};
class Student : protected Person
{
private:
    double _gpa;
};
class Teacher : protected Person
{
private:
    string _title;
};
int main()
{
    Person p;
    Student s;
    cout << p._name << endl;
    cout << s._name << endl;
    return 0;
}

在这里插入图片描述

  • 可以看到我们可以调用Person类的_name,因为它是公有成员变量
  • 但是被Student类进行protected继承后,在Student类中_name就变为了protected的成员变量,我们不能直接访问

二:基类与派生类间的转换

  • 派生类的对象可以直接赋值给基类的对象,指针,引用,这种行为也叫做切片
  • 实际上就是把派生类中基类中的那部分切下然后赋值过去
    • 但是基类不能赋值给派生类
      在这里插入图片描述
int main()
{
    Person p;
    Student s;
    p = s;//派生类赋值给基类对象
    Person* ptr = &s;//派生类的地址给基类的指针
    Person& ref = s;//基类的引用指向派生类
    return 0;
}

三:继承的作用域

  • 基类与派生类拥有独立的作用域
  • 当子类和父类有同名成员或函数时,子类成员将会屏蔽父类对同名成员的直接访问,这种情况叫做隐藏或者重定向(要想访问基类成员需要加上基类类名和域作用限定符,比如Person::_name
  • 此时基类和派生类函数名相同可能会被误理解为函数重载,但是由于基类和派生类的作用域不同,所以它们是不构成重载的,构成函数重载需要在同一个作用域中
  • 并且基类和派生类只要函数名相同就构成隐藏,不管参数类型是否相同
  • 推荐不要在基类和派生类中定义同名成员
    在这里插入图片描述

四:派生类的默认成员函数

<1> 构造函数

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

<2>拷贝构造

  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化

<3>赋值运算符重载

  • 派生类的operator=必须要调用基类的operator=完成基类的复制

<4>析构函数

  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
  • 所以这里我们不用自己调用基类的析构函数,不然基类的析构函数会被调用两次,可能造成申请的空间的重复释放
class Person
{
public:
    Person(const string& name, const string& id)
        :_name(name)
        , _id(id)
    {};
    Person(const Person& p)
        :_name(p._name)
        , _id(p._id)
    {};
    Person& operator=(const Person& p)
    {
        if(this!=&p)
        {
            _name = p._name;
            _id = p._id;
        }
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
    void Show()
    {
        cout << "name: " << _name << endl;
        cout << "id: " << _id << endl;
    }
private:
    string _name;
    string _id;
};
class Student : public Person
{
public:
    Student(const string& name, const string& id, double gpa)
        :Person(name, id)
        , _gpa(gpa)
    {};
    Student(const Student& s)
        :Person(s)
        , _gpa(s._gpa)
    {};
       Student& operator=(const Student& s)
    {
           if (this != &s)
           {
               Person::operator=(s);
               _gpa = s._gpa;
           }
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
    void Show()
    {
        Person::Show();
        cout << "gpa: " << _gpa << endl;
    }
private:
    double _gpa;
};

五:友元与静态成员

<1>友元

  • 友元关系是不能继承的,也就是说基类的友元并不是派生类的友元,它不能访问派生类的私有成员
    在这里插入图片描述
  • 如图,Show函数是Person的友元,可以访问Person类的私有成员
  • 但是不能访问继承了Person类的Student类的私有成员

<2> 静态成员

  • 基类定义了一个static静态成员,则整个继承体系中只有一个这样的成员
  • 也就是说不会像非静态成员一样,子类成员中也有一个相同的成员
  • 利用这个特性可以计算出我们究竟创建了多少派生类对象和基类对象
    在这里插入图片描述

六:菱形继承与虚继承

在这里插入图片描述

  • 如图所示,一个类继承了两个类,并且这两个类继承了同一个基类,这就形成了菱形继承
  • 这会导致很多问题
  • 比如Person的成员变量在Student类中有一份,在Teacher类中有一份,并且这两份完全一样,都被Assistant继承了,但其实Assitant只要一份就够了,导致了代码冗余
  • 并且导致了二义性,我们使用_name到底是Student类里面的还是Teacher类中的,这虽然可以通过指定作用域解决,但还是有不便之处
    在这里插入图片描述
  • 为了解决上述不便之处,我们可以使用虚继承
  • 即在Student类和Teacher类继承时在前面加上virtual关键字
  • 就像这样class Student: virtual public Person
class Person
{
public:
    int _a;
};
class Student : public Person
{
public:
    int _b;
};
class Teacher : public Person
{
public:
    int _c;
};
class Assistant : public Student , public Teacher 
{
public:
    int _d;
};
int main()
{
    Assistant tmp;
    tmp.Student::_a = 1;
    tmp.Teacher::_a = 2;
    tmp._b = 3;
    tmp._c = 4;
    tmp._d = 5;
    return 0;
}

在这里插入图片描述

  • 可以看到不使用虚继承会有两个_a变量存在,代码冗余
    ![![- 在这里插入图片描述](https://img-blog.csdnimg.cn/05b4d809e264421391a72406a8c902ba.png)
  • 而我们使用了虚继承之后,就只有了一个变量_a,放在对象的最下方,_a同时属于Student和Teacher类,Student类和Teacher类每个类都存了一个指针,叫做虚基表指针
  • 这个虚基表指针指向一张表,那张表中存储了指针所在的位置离_a位置的偏移量,通过这个偏移量就能找到_a

七:继承与组合

  • 除了继承,类直接还能组合,即有一个类A,一个类B,类B是类A的一个成员变量
  • 这就是A组合B,是一种has-a的关系,相当于黑箱复用,类A只能调用类B的公有接口,B对A不透明
  • 而继承相当于白箱复用,是一种is-a的关系,B对A是透明的,这一定程度上破坏了封装
  • 能用组合的地方就少用继承,因为组合的耦合度低,一个类修改了对另一个类影响很小,而继承中基类修改了对派生类影响很大
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++类的三大特性之继承 的相关文章

  • 查找哪些页面不再与写入时复制共享

    假设我在 Linux 中有一个进程 我从中fork 另一个相同的过程 后forking 因为原始进程将开始写入内存 Linux写时复制机制将为进程提供与分叉进程使用的不同的唯一物理内存页 在执行的某个时刻 我如何知道原始进程的哪些页面已被写
  • OpenCv读/写视频色差

    我试图简单地使用 openCV 打开视频 处理帧并将处理后的帧写入新的视频文件 我的问题是 即使我根本不处理帧 只是打开视频 使用 VideoCapture 读取帧并使用 VideoWriter 将它们写入新文件 输出文件看起来比输入更 绿
  • asp.net 文本框文本模式数字,仅允许数字

    我只是想知道 ASP NET 中是否有一种方法只允许文本框中的数字textmode number 当我使用这个时
  • 迭代变量并查找特定类型实例的技术

    我想迭代进程中内存中的变量 通过插件动态加载 并查找特定类型的实例 以前我可以找到特定类型 或内存中的所有类型 我可以创建类型的实例 我可以获取作为不同类型的字段包含的实例 但我无论如何都不知道只是 搜索 特定类型的实例 一种方法是使用 W
  • 使用具有现有访问令牌的 Google API .NET 客户端

    用例如下 移动应用程序正在通过 Google 对用户进行身份验证 并且在某些时候 我们需要将用户的视频发布到他的 YouTube 帐户 出于实际原因 实际发布应该由后端完成 已经存储在那里的大文件 由于用户已经通过应用程序的身份验证 因此应
  • C#动态支持吗?

    看完之后这个帖子 https stackoverflow com questions 2674906 when should one use dynamic keyword in c sharp 4 0k和链接 我还有 2 个问题 问题 1
  • Clang 编译器 (x86):80 位长双精度

    我正在尝试在 x86 Windows 平台上使用本机 80 位长双精度 海湾合作委员会选项 mlong double 80 https gcc gnu org onlinedocs gcc x86 Options html似乎不适用于 cl
  • 从多个类访问串行端口

    我正在尝试使用串行端口在 arduino 和 C 程序之间进行通信 我对 C 编程有点陌生 该程序有多种用户控制形式 每一个都需要访问串口来发送数据 我需要做的就是从每个类的主窗体中写入串行端口 我了解如何设置和写入串行端口 这是我的 Fo
  • 如何从网站下载 .EXE 文件?

    我正在编写一个应用程序 需要从网站下载 exe 文件 我正在使用 Visual Studio Express 2008 我正在使用以下代码 private void button1 Click object sender EventArgs
  • Azure 事件中心 - 按顺序接收事件

    我使用下面的代码从 Azure Event Hub 接收事件 https learn microsoft com en us azure event hubs event hubs dotnet framework getstarted s
  • C# 中条件编译符号的编译时检查(参见示例)?

    在 C C 中你可以这样做 define IN USE 1 define NOT IN USE 1 define USING system 1 system 1 IN USE 进而 define MY SYSTEM IN USE if US
  • 在 C 中使用 GNU automake 中的解析器

    我是 GNU autotools 的新手 在我的项目中使用了 lex 和 yacc 解析器 将它们作为 makefile am 中的源代码会产生以下错误 配置 in AC CHECK PROGS YACC bison yacc none i
  • 将代码拆分为标头/源文件

    我从 Asio 的示例页面中获取了以下代码 class tcp connection public boost enable shared from this
  • 是否可以有一个 out ParameterExpression?

    我想定义一个 Lambda 表达式out范围 有可能做到吗 下面是我尝试过的 C Net 4 0 控制台应用程序的代码片段 正如您在 procedure25 中看到的 我可以使用 lambda 表达式来定义具有输出参数的委托 但是 当我想使
  • 当前的 x86 架构是否支持非临时加载(来自“正常”内存)?

    我知道有关此主题的多个问题 但是 我没有看到任何明确的答案或任何基准测量 因此 我创建了一个处理两个整数数组的简单程序 第一个数组a非常大 64 MB 第二个数组b很小 无法放入 L1 缓存 程序迭代a并将其元素添加到相应的元素中b在模块化
  • strcmp 给出分段错误[重复]

    这个问题在这里已经有答案了 这是我的代码给出分段错误 include
  • 什么是 __declspec 以及何时需要使用它?

    我见过这样的例子 declspec在我正在阅读的代码中 它是什么 我什么时候需要使用这个构造 这是 Microsoft 对 C 语言的特定扩展 它允许您使用存储类信息来赋予类型或函数属性 文档 declspec C https learn
  • 转到定义:“无法导航到插入符号下的符号。”

    这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新的答案或互动 我今天突然开始在我的项目中遇到一个问题 单击 转到定义 会出现一个奇怪的错误 无法导航到
  • 使用 CSharpCodeProvider 类编译 C# 7.3 的 C# 编译器版本是什么?

    我想使用 Microsoft CSharp CSharpCodeProvider 类来编译 C 7 3 代码 编译器版本在 IDictionary 中指定 在创建新的 CSharpCodeProvider 时将其作为输入 例如 Compil
  • 使用 Crypto++ 获取 ECDSA 签名

    我必须使用 Crypto 在变量中获取 ECDSA 签名 我在启动 SignMessage 后尝试获取它 但签名为空 我怎样才能得到它 你看过 Crypto wiki 吗 上面有很多东西椭圆曲线数字签名算法 http www cryptop

随机推荐

  • mongoTemplate操作MongoDB排序

    解决项目中遇到的排序问题 Mark一下 Override public List
  • 【定时将hbase的索引同步到solr的core,当同步失败时,回滚core】好记性不如烂笔头,我将工作中写的自动化脚本记录在此,供大家参考

    前言 此脚本不包含core的创建 创建core请移步他处 本贴侧重core快照的创建 快照状态查询 core的删除 从快照恢复core hbase到solr的同步不做为本贴的重点 同步脚本syn solr sh内容 binbash 定义co
  • 虚拟机使用教程

    文章目录 前言 1 什么是母机与子机 2 常用快捷键 一 如何开机 二 如何克隆及删除虚拟机 三 如何修改硬件信息 改机器码 四 虚拟机内外如何传文件 五 调整虚拟机窗口大小及虚拟机全屏显示 六 如何调整cpu 内存 七 虚拟机开启声音 不
  • latex 约等于且大于 小于

    约等于 a approx b gt approx 大于约等于 a gtrsim b gt gtrsim 小于约等于 a
  • 基于QT 实现的LearnGL例子

    LOpenGL 是学习OpenGL非常好的资料 网址是 LearnOpenGL CN learnopengl cn github io 最近复习OpenGL 基于QT 拷贝实现了LearnGL的一些例子 下载地址 QT OpenGL 学习基
  • UML_类图

    在UML类图中 常见的有以下几种关系 泛化 Generalization 实现 Realization 关联 Association 聚合 Aggregation 组合 Composition 依赖 Dependency 1 泛化 Gene
  • 【Python】python 3.x 数据类型 吐血汇总

    文章目录 1 整数型 2 浮点数 3 字符串 4 布尔值 5 空值 6 变量 7 定义字符串 1 raw字符串 多行字符串 2 Unicode字符串 8 集合 list 1 访问列表中的值 2 更新列表 3 删除列表元素 4 Python列
  • 数据处理中的标准化、归一化,究竟是什么?

    原文链接 数据处理中的标准化 归一化 究竟是什么 大家好 我是小一 今天说一个比较重要的内容 无论是在算法建模还是在数据分析都比较常见 数据归一化和标准化 开始之前 请你先把网上看到的所有相关的博客 帖子都忘掉 不说全部 能讲清楚这个概念的
  • T5模型简单介绍

    目录 一 概要 二 深入扩展 2 1 两个要素 2 2 预训练方法 一 概要 谷歌公司的研究人员提出的 T5 Text to Text Transfer Transformer 有5个T开头的单词 所以叫做T5 模型采用了一种与前述模型截然
  • 成功解决Python中ValueError: not enough values to unpack (expected 10, got 2)故障

    对split函数不熟悉 将1修改为10即成功 同时将文件中所有空格键替换为Tab键 个人觉得这一步没啥用 这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题 有助于目录的生成 如何改变文本的样式 插入
  • linux screen rz/sz 文件卡死了快速退出的方法

    项目场景 服务器上通过sz下载txt格式的数据保存于本地遇到问题 问题描述 Linux 中使用tmux screen rz sz 命令下载文件卡死 出现如下图所示情况 快速退出方法 有如下解决办法 按住Ctrl键 再按五次x键 强行终断传输
  • 深入解析Spring Boot中最常用注解的使用方式(上篇)

    摘要 本文将详细介绍Spring Boot中最常用的注解的使用方式 并通过代码示例加以说明 通过学习这些注解 读者将能够更好地理解和运用Spring Boot框架 构建高效的企业级应用 目录 第一部分 常见的控制器注解 1 RequestM
  • Springboot项目打瘦包(将依赖包放到jar包外)

    一般springboot项目我们不做任何配置的话 打包会包含很多的依赖 生成的包过大 动辄100M以上 往生产环境上传很慢 所以把所有用的jar包打到外部 这样生成的小包中只有自已开发的程序 基本可以控制在1M以内 效果甚好 首先把spri
  • How to set IE proxy using VB.NET

    Created by SharpDevelop User Ying Shen Date 2004 11 12 Time 11 16 To change this template use Tools Options Coding Edit
  • NumPy 高级索引

    NumPy 高级索引 NumPy 比一般的 Python 序列提供更多的索引方式 除了之前看到的用整数和切片的索引外 数组可以由整数数组索引 布尔索引及花式索引 整数数组索引 以下实例获取数组中 0 0 1 1 和 2 0 位置处的元素 i
  • 2020年华为杯第十七届中国研究生数学建模竞赛---回顾记录

    这次做一个复盘贴 刚刚参加完这次的华为杯数学建模 由于是2020级研究生所以允许跨校组队 所以选择的队友是两个外校的 沟通方面第一天至第三天每天在微信使用语音通话汇报一次进度 第四天至第五天腾讯会议共享桌面进行论文修改 比赛时间 2020年
  • 网络安全-反序列化漏洞简介、攻击与防御

    目录 简介 PHP序列化 Python序列化 攻击 PHP举例 Python举例 防御 参考 简介 各种语言都有反序列化漏洞 Java PHP Python等 序列化即将对象转化为字节流 便于保存在文件 内存 数据库中 反序列化即将字节流转
  • 帮你理解网关、ARP、IP、MAC、路由

    我发个简单形象的小故事 你一看就明白了 假设你叫小不点 本地主机 住在一个大院子 本地局域网 里 有很多邻居 网络邻居 门 口传达室有个看大门的李大爷 李大爷就是你的网关 当你想跟院子里的某个伙伴玩 只要你在院 子里大喊一声他的名字 pin
  • Linux发送接收邮件

    目录 一 实验 1 linux用户发送给linux中的其它用户 2 linux用户发送给外网用户 一 实验 1 linux用户发送给linux中的其它用户 1 使用命令 yum install y sendmail 安装sendmail软件
  • C++类的三大特性之继承

    目录 一 继承的概念与使用 lt 1 gt 什么是继承 lt 2 gt 如何使用 二 基类与派生类间的转换 三 继承的作用域 四 派生类的默认成员函数 lt 1 gt 构造函数 lt 2 gt 拷贝构造 lt 3 gt 赋值运算符重载 lt