【C++】---继承

2023-11-01

继承的概念与定义

继承是面向对象编程三大特性之一,是一种可以使代码复用最重要的手段,在原有类特性的基础上进行扩展,产生新的类。例如人和学生的关系,学生是一个人,那么人所有的特性学生都有,比如人有姓名、身份证号码、性别等,这些学生也有具备。在此基础上学生还有额外的特性,例如学号,班级

为了方便的去创建类,因此我们可以在人这个类的基础上去创建出新的学生类,当我们使用继承时,那么在创建学生类时就不用再去定义人这个类的所有特性

class Person
{
public:
    void Print()
    {
    	cout << "name:" << _name << endl;
    	cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};

class Student : public Person
{
    protected:
    int _stuid; // 学号
};

像上面的代码就是使用了继承去创建学生类,那么当我们实例化对象之后,学生类的对象就也会包含了人类的所有成员

image-20230406124028551

像这种继承我们就可以称人的类为父类或者基类,学生这个类就称为子类或者派生类。

主要注意:友元关系不能继承,当基类定义了静态成员那么整个继承体系里面只有一个这样的成员

继承的定义格式

image-20230406124425518

继承方式可以有三种,也就是我们所知道的三种访问限定方式。那么不同的继承方式也就意味着,派生类能够继承基类的不同特性

公有继承时,派生类可以访问基类除私有成员外的所有成员

保护继承时,只能访问基类中的保护成员

私有继承时,基类中的所有成员都不能访问

基类中的私有成员不论什么继承方式都不可以访问

父类和子类的对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 ,也就是说可以用基类的指针或者引用去指向派生类的对象,要注意基类指向派生类可以,但是派生类不可以指向基类。这种方式可以形象的称为切片

int main() {
    Student s;
    // 1.子类对象可以赋值给父类对象/指针/引用
    Person p = s;
    Person* pp = &s;
    Person& rp = s;
    //2.基类对象不能赋值给派生类对象
    //s = p;

    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &s;

    Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
    ps1->_stuid = 10;
    pp = &p;
    Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
        ps2->_stuid = 10;

    return 0;
}

image-20230406140241226

继承中的作用域

在继承体系中基类和派生类都有独立的作用域 ,如果基类和派生类中都有着一个同名的函数,那么派生类对象就会自动屏蔽对基类同名函数的访问,这种情况称为隐藏/重定义

class Person
{
public:
    void Print()
    {
        cout << "Person:" << _name << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};

class Student : public Person
{
public:
    void Print()
    {
        cout << "Student:" << _name << endl;
    }

protected:
    int _stuid; // 学号
};

int main() {
    Person p;
    Student s;

    s.Print();

    return 0;
}

image-20230406140619509

可以看到这种情况访问的就是派生类中的函数。还有一种情况需要注意 如果派生类和基类的同名函数在派生类里面带有参数,但我们调用该函数并且不给函数传参的时候程序就无法运行通过。因为基类中的函数已经被隐藏了,所以就找不到对应的函数去调用

image-20230406141023900

子类的默认成员函数

派生类的6个默认函数的生成规则

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

菱形继承

继承会有单继承和多继承。单继承就是一个类继承一个类,多继承就是多个类继承一个类

image-20230406141701709

那么因为有多继承的存在,则会产生出一种菱形继承的现象

image-20230406142013775

如图所示,它们之间的继承关系形成了一个菱形形状,那么这种菱形继承会导致什么问题呢。

首先,因为学生和老师的类中都继承了一份人的类,接着助理又去继承学生和老师这两个类,那么也就是说助理这个类就会有两份人的类的属性。

class Person
{
public:
    void Print()
    {
        cout << "Person:" << endl;
    }
public:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};

class Student : public Person
{
public:
    void Print()
    {
        cout << "Student:" << endl;
    }

public:
    int _stuid; // 学号
};

class Teacher : public Person
{
public:
    void Print(int n)
    {
        cout << "Teacher:" << endl;
    }

public:
    int _teaid; // 学号
};

class Assistant : public Student , public Teacher
{
public:
    void Print(int n)
    {
        cout << "Assistant:" << endl;
    }

public:
    int _assid; // 学号
};

int main() {
    Person p;
    Student s;
    Teacher t;
    Assistant a;

    return 0;
}

image-20230406142818901

可以看到a对象里面有两个 _name , __age,所以如果这时候我们去访问a的name就会出现问题,因为编译器根本就不知道你想要访问的是哪一个。这样就造成了数据的冗余和二义性

虚拟继承

那么为了解决这种菱形继承的问题,我们可以采用一种方法叫做虚拟继承。在继承方式前加上 virtual 就可以定义为虚拟继承

为了研究虚拟继承,首先先建立一个菱形继承体系,运行起来之后利用内存窗口查看数据

class A
{
public:
	int _a;
};

// class B : public A
class B : virtual public A
{
public:
	int _b;
};

// class C : public A
class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

image-20230406145039792

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A

如上图,b的虚基表地址为 内存2窗口的地址,对应内存1的青蓝色框,通过这个地址找到虚基表后可以看到一个数字 28 这个机会表里存的偏移量,就可以通过这个偏移量往下找到A。c的虚拟表也同理,窗口3里可以看出。

通过这个方式,可以通过偏移量去修改数据这样就可以避免了数据的冗余和二义性了。

总结

多继承可以认为是C++的缺陷,因为会导致菱形继承的发生。

继承是一个依赖关系很强,耦合度很高的方法,所以在日常编写程序时,不是那种特别特定的关系能不用继承就不继承,后面的多态要靠继承实现。否则可以多用组合。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】---继承 的相关文章

随机推荐

  • QDockWidget设置初始大小的一个解决方案

    QDockWidget仅仅作为其子控件的一个包装 因此对其使用resize 或者setGeometry 均不管用 这造成QDockWidget的初始大小常常不尽如人意 网上有前辈提出通过调用QDockWidget的setMaximumSiz
  • ElementUI浅尝辄止34:Radio 单选框

    在一组备选项中进行单选 1 如何使用 由于选项默认可见 不宜过多 若选项过多 建议使用 Select 选择器 要使用 Radio 组件 只需要设置v model绑定变量 选中意味着变量的值为相应 Radio label属性的值 label可
  • 工业设备自动化平台的一些杂想

    今天是愚人节 浦西开始正式封闭 所有人都被关在家里足不出户 等待着不知道何时开始的核酸检测 一时间也不知道能做些啥 闲的发慌 总得找点事情做做 翻了会CLR的书 又背了会单词 总感觉心不静 回想起之前被人问过的工业设备自动化平台的问题 突然
  • 非递归实现二叉树遍历(附c++完整代码)

    先序 中序和后序遍历过程 遍历过程中经过结点的路线一样 只是访问各结点的时机不同 从图中可以看到 前序遍历在第一次遇见元素时输出 中序遍历在第二次遇见元素时输出 后序遍历在第三次遇见元素时输出 非递归算法实现的基本思路 使用堆栈 一 前序遍
  • 【Android Studio】android.database.sqlite.SQLiteException: no such table: XXX (code 1 SQLITE_ERROR)

    问题描述 android database sqlite SQLiteException no such table XXX code 1 SQLITE ERROR while compling select from xxxx 参考了各种
  • 字符替换C++

    题目描述 把一个字符串中特定的字符全部用给定的字符替换 得到一个新的字符串 输入 只有一行 由一个字符串和两个字符组成 中间用单个空格隔开 字符串是待替换的字符串 字符串长度小于等于1000个字符 且不含空格等空白符 接下来一个字符为需要被
  • FileReader读取文件

    前言 FileReader是一种异步文件读取机制 结合input file可以很方便的读取本地文件 input file 在介绍FileReader之前 先简单介绍input的file类型
  • doxygen教程-6-把注释放在其他位置

    文章目录 把注释放到成员之后 注释在头文件和源文件的分布 Structural commands 把注释放到 几乎 任意位置 只有先注释高层级的结构 才会显示低层级的注释 把注释放到成员之后 这里出现了新词 成员 member 在官方手册里
  • ubuntu18.04更新clang的版本

    可选 添加bionic相关的源 1 备份 etc apt sources list 2 添加源到sources list文件最后 sudo vim etc apt sources list deb http archive ubuntu c
  • 模型结构可视化神器——Netron(支持tf, caffe, keras,mxnet等多种框架)

    转自 https blog csdn net leviopku article details 81980249 很多时候 复现人家工程的时候 需要了解人家的网络结构 但不同框架之间可视化网络层方法不一样 这样给研究人员造成了很大的困扰 前
  • 【华为OD机试真题2023 JS】几何平均值最大子数组

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 几何平均值最大子数组 知识点数组二分查找 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 从一个长度为N的正数数组numbers中找出长度至少为L且几何平均值最大
  • Kubernetes1.91(K8s)安装部署过程(四)--Master节点安装

    再次明确下架构 三台虚拟机 centos 7 4系统 docker为17版本 ip为10 10 90 105到107 其中105位master 接下来的master相关组件安装到此机器上 etcd集群为3台 分别复用这3台虚拟机 作为k8s
  • Docker安装(使用阿里云镜像)

    Docker安装 使用阿里云镜像 Docker从1 13版本之后采用时间线的方式作为版本号 分为社区版CE和企业版EE 社区版是免费提供给个人开发者和小型团体使用的 企业版会提供额外的收费服务 比如经过官方测试认证过的基础设施 容器 插件等
  • webp图片在html上显示,WebP 格式图片在实际项目中的使用方式

    一 前言 关于 WebP 的支持情况 以及适用场景此处不做详细说明 具体见官方文档 先说结论 目前 WebP 支持情况占比较大 数据如下 在适合的场景下可以使用 WebP 格式图片来提高页面加载速度 支持的浏览器占比达到近90 数据来源 C
  • word 安装

    亲测可用 记录一下 安装word步骤 office tool plus 概述 安装激活 1 下载安装 2 解压 3 移除 4 安装部署 5 激活 总结 概述 Office Tool Plus 是一个强大的 Office 部署工具 可以很方便
  • CentOS7安装Docker&配置阿里云镜像仓库

    安装Docker 1 安装yum utils包 yum install y yum utils 2 设置yum源为阿里云 提升下载速度 yum config manager add repo http mirrors aliyun com
  • Unity3D碰撞检测和OnTriggerEnter用法

    在目前掌握的情况分析 在Unity中参与碰撞的物体分2大块 1 发起碰撞的物体 2 接收碰撞的物体 1 发起碰撞物体有 Rigodbody CharacterController 2 接收碰撞物体由 所有的Collider 工作的原理为 发
  • 机器学习笔记 - 使用 ResNet-50 和余弦相似度的基于图像的推荐系统

    一 简述 这里的代码主要是基于图像的推荐系统 该系统利用 ResNet 50 深度学习模型作为特征提取器 并采用余弦相似度来查找给定输入图像的最相似嵌入 该系统旨在根据所提供图像的视觉内容为用户提供个性化推荐 二 所需环境 Python 3
  • 【论文不精读】Reinforced Path Reasoning for Counterfactual Explainable Recommendation

    Reinforced Path Reasoning for Counterfactual Explainable Recommendation 1 Introduction 现代推荐系统在对复杂的用户或者商品上下文进行建模时变得十分复杂并且
  • 【C++】---继承

    文章目录 继承的概念与定义 继承的定义格式 父类和子类的对象赋值转换 继承中的作用域 子类的默认成员函数 菱形继承 虚拟继承 总结 继承的概念与定义 继承是面向对象编程三大特性之一 是一种可以使代码复用最重要的手段 在原有类特性的基础上进行