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++类的三大特性之继承 的相关文章

  • 使用 mono/nunit-console/4 在 Mac OS X 控制台上运行测试

    我安装了 Max OS X 10 11 1 上面装有 Xamarin 我编写了简单的测试类 只是为了测试在 Mac OS X 和 Ubuntu 上运行 Nunit 测试 该类实际上有一个返回字符串的方法 using System names
  • 在实体框架拦截器中向 DbScanExpression 添加内部联接

    我正在尝试使用实体框架 CommandTree 拦截器通过 DbContext 向每个查询添加过滤器 为了简单起见 我有两个表 一个称为 User 有两列 UserId 和 EmailAddress 另一个称为 TenantUser 有两列
  • 我如何理解这个 C 类型声明?

    double bar int double double double double 在查看讲座幻灯片时 我发现了留给学生的练习 用简单的英语来说 什么是类型bar在这个 C 声明中 Please帮助我解决这个问题 我什至不知道从哪里开始
  • 更改 Qt OpenGL 窗口示例以使用 OpenGL 3.3

    我正在尝试更改 Qt OpenGL 示例以使用更现代的 opengl 版本 330 似乎合适 所以我做了 在 main cpp 上设置版本和配置文件 设置着色器版本 更改着色器以使用统一 它现在构建没有任何错误 但我只看到一个空白窗口 我错
  • 如何在 C# / .NET 中创建内存泄漏[重复]

    这个问题在这里已经有答案了 可能的重复 托管代码中是否可能存在内存泄漏 特别是 C 3 0 https stackoverflow com questions 6436620 is it possible to have a memory
  • 平滑滚动.net 表单

    您好 我正在 net 中使用表单 并且在运行时动态添加大量链接标签 我将这些链接标签添加到面板并将该面板添加到 winform 当链接标签的数量增加时 表单会显示一个自动滚动条 垂直 现在 当我使用自动滚动向下滚动时 表单在滚动时不会更新其
  • 读取 C# 中的默认应用程序设置

    我的自定义网格控件有许多应用程序设置 在用户范围内 其中大部分是颜色设置 我有一个表单 用户可以在其中自定义这些颜色 并且我想添加一个用于恢复默认颜色设置的按钮 如何读取默认设置 例如 我有一个名为的用户设置CellBackgroundCo
  • 防止 boost::asio::io_context 在空轮询调用时停止

    此代码调用发布的句柄 boost asio io context ioc boost asio post ioc std cout lt lt lol lt lt std endl ioc poll 而这并没有 boost asio io
  • 为什么这个没有特殊字符的正则表达式会匹配更长的字符串?

    我正在使用此方法来尝试查找匹配项 例如 Regex Match A2 TS OIL TS OIL RegexOptions IgnoreCase Success 我得到了真实的结果 我很困惑 我认为这应该返回 false 因为模式中没有特殊
  • 如何在 C# 控制台应用程序中将修饰符(ctrl、alt、shift)按键捕获为单个按键?

    Console ReadKey 仅在按下 正常 键时捕获输入 然后将修饰符 如果有 附加为键信息的一部分 如何将单个修饰键注册为输入 提供了一种解决方案这个链接 https blogs msdn microsoft com toub 200
  • fprintf() 线程安全吗?

    我正在为野人就餐问题的某些变量编写一个 C 解决方案 现在 我创建线程 每个线程都将 FILE 获取到同一个调试文件 在线程内我正在使用 fprintf 进行一些打印 打印的语句不受任何类型的互斥锁等保护 我没有在调试文件中观察到任何交错行
  • 单例模式和 std::unique_ptr

    std unique ptr唯一地控制它指向的对象 因此不使用引用计数 单例确保利用引用计数只能创建一个对象 那么会std unique ptr与单例执行相同 单例确保只有一个实例属于一种类型 A unique ptr确保只有一个智能指针到
  • C++ php 和静态库

    我创建了一个library a 其中包含 cpp 和 h 文件 其中包含很多类 嵌套类和方法 我想在 php 示例中包含这个静态库并尝试使用它 我想提一下 我是 php 新手 我已经在 test cpp 文件中测试了我的 libray a
  • 检查 RoutedEvent 是否有任何处理程序

    我有一个自定义 Button 类 当单击它时 打开特定窗口 它总是执行相同的操作 我添加了一个可以在按钮的 XAML 中分配的 Click 事件 就像常规按钮一样 当它被单击时 我想执行 Click 事件处理程序 如果已分配 否则我想执行默
  • 运行选定的代码生成器时出错:“未将对象引用设置到对象的实例。”错误?

    我已经尝试了所有解决方案 例如修复 VS 2013 但没有用 当您通过右键单击控制器文件夹来创建控制器并添加控制器时 然后右键单击新创建的控制器的操作并选择添加视图 当我尝试创建视图时 就会发生这种情况 它不是一个新项目 而是一个现有项目
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • 在 EnvDTE 中调试时捕获 VS 局部变量

    是否可以使用 EnvDTE 进行 vsix Visual Studio 扩展来捕获本地和调试窗口使用的调试数据 或者可以通过其他方法吗 我想创建一个自定义的本地窗口 我们可以修改它以根据需要显示一些较重的内容 而无需为高级用户牺牲原始的本地
  • 了解使用 Windows 本机 WPF 客户端进行 ADFS 登录

    我已经阅读了大量有关 ADFS 与 NodeJS Angular 或其他前端 Web 框架集成以及一般流程如何工作的文献 并通过 Auth0 Angular 起始代码构建了概念证明 但我不明白如何这可以与本机 WPF Windows 应用程
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • 从 JavaScript 中的 OnClientClick 事件中阻止 C# 中的 asp:Button OnClick 事件?

    我有一个asp Button在我的网页上 它调用 JavaScript 函数和代码隐藏方法 后者进行调用以导航到另一个页面 在 JavaScript 函数中 我正在检查条件 如果不满足这个条件 我想中止导航 以便OnClick方法未被调用

随机推荐

  • 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