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

  • 结构化绑定中缺少类型信息

    我刚刚了解了 C 中的结构化绑定 但有一件事我不喜欢 auto x y some func is that auto正在隐藏类型x and y 我得抬头看看some func的声明来了解类型x and y 或者 我可以写 T1 x T2 y
  • 没有特殊字符的密码验证器

    我是 RegEx 的新手 已经进行了大量搜索 但没有找到任何具体内容 我正在编写一个验证密码字符串的正则表达式 可接受的字符串必须至少具有 4 种字符类型中的 3 种 数字 小写字母 大写字母 特殊字符 我对包含有一个想法 也就是说 如果这
  • 根据属性的类型使用文本框或复选框

    如果我有这样的结构 public class Parent public string Name get set public List
  • 机器Epsilon精度差异

    我正在尝试计算 C 中双精度数和浮点数的机器 epsilon 值 作为学校作业的一部分 我在 Windows 7 64 位中使用 Cygwin 代码如下 include
  • 如何从 Visual Studio 将视图导航到其控制器?

    问题是解决方案资源管理器上有 29 个项目 而且项目同时具有 ASP NET MVC 和 ASP NET Web 表单结构 在MVC部分中 Controller文件夹中有大约100个子文件夹 每个文件夹至少有3 4个控制器 视图完全位于不同
  • free 和 malloc 在 C 中如何工作?

    我试图弄清楚如果我尝试 从中间 释放指针会发生什么 例如 看下面的代码 char ptr char malloc 10 sizeof char for char i 0 i lt 10 i ptr i i 10 ptr ptr ptr pt
  • 对类 static constexpr 结构的未定义引用,g++ 与 clang

    这是我的代码 a cp p struct int2 int x y struct Foo static constexpr int bar1 1 static constexpr int2 bar2 1 2 int foo1 return
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • 两个类可以使用 C++ 互相查看吗?

    所以我有一个 A 类 我想在其中调用一些 B 类函数 所以我包括 b h 但是 在 B 类中 我想调用 A 类函数 如果我包含 a h 它最终会陷入无限循环 对吗 我能做什么呢 仅将成员函数声明放在头文件 h 中 并将成员函数定义放在实现文
  • 实例化类时重写虚拟方法

    我有一个带有一些虚函数的类 让我们假设这是其中之一 public class AClassWhatever protected virtual string DoAThingToAString string inputString retu
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • LINQ:使用 INNER JOIN、Group 和 SUM

    我正在尝试使用 LINQ 执行以下 SQL 最接近的是执行交叉联接和总和计算 我知道必须有更好的方法来编写它 所以我向堆栈团队寻求帮助 SELECT T1 Column1 T1 Column2 SUM T3 Column1 AS Amoun
  • C# 动态/expando 对象的深度/嵌套/递归合并

    我需要在 C 中 合并 2 个动态对象 我在 stackexchange 上找到的所有内容仅涵盖非递归合并 但我正在寻找能够进行递归或深度合并的东西 非常类似于jQuery 的 extend obj1 obj2 http api jquer
  • 复制目录下所有文件

    如何将一个目录中的所有内容复制到另一个目录而不循环遍历每个文件 你不能 两者都不Directory http msdn microsoft com en us library system io directory aspx nor Dir
  • C 函数 time() 如何处理秒的小数部分?

    The time 函数将返回自 1970 年以来的秒数 我想知道它如何对返回的秒数进行舍入 例如 对于100 4s 它会返回100还是101 有明确的定义吗 ISO C标准没有说太多 它只说time 回报 该实现对当前日历时间的最佳近似 结
  • 对于某些 PDF 文件,LoadIFilter() 返回 -2147467259

    我正在尝试使用 Adob e IFilter 搜索 PDF 文件 我的代码是用 C 编写的 我使用 p invoke 来获取 IFilter 的实例 DllImport query dll SetLastError true CharSet
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • 从 mvc 控制器使用 Web api 控制器操作

    我有两个控制器 一个mvc控制器和一个api控制器 它们都在同一个项目中 HomeController Controller DataController ApiController 如果我想从 HomeController 中使用 Dat

随机推荐

  • 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