【C++】继承(下)

news2024/10/1 17:35:32

在这里插入图片描述

个人主页~

继承(上)~


继承

  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承以及菱形虚拟继承
    • 1、菱形继承
    • 2、菱形虚拟继承
  • 八、继承的总结与反思
    • 继承和组合

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

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

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

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

派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序

派生类对象初始化先调用基类构造再调派生类构造

派生类对象析构清理先调用派生类析构再调基类的析构

需要值得注意的是,构造时要先父后子,析构时要先子后父
对于构造来说,因为子类是继承来的,所以一定是先父后子,对于析构来说,在子类中可能会有访问父类成员的成员在,当父类先析构了,再析构子类就会存在风险,所以析构要先子后父

这个程序中的打印信息可以帮助我们确认构造的过程

class Person
{
public:
    Person(const char* name = "little_monster")
        : _name(name)
    {
        cout << "Person()" << endl;
    }

    Person(const Person& p)
        : _name(p._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;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};

class Student : public Person
{
public:
    Student(const char* name, int num)
        : Person(name)
        , _num(num)
    {
        cout << "Student()" << endl;
    }

    Student(const Student& s)
        : Person(s)
        , _num(s._num)
    {
        cout << "Student(const Student& s)" << endl;
    }

    Student& operator = (const Student& s)
    {
        cout << "Student& operator= (const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator =(s);
            _num = s._num;
        }
        return *this;
    }

    ~Student()
    {
        cout << "~Student()" << endl;
    }
protected:
    int _num; //学号
};

void Test()
{
    Student s1("little", 18);
    Student s2(s1);
    Student s3("monster", 17);
    s1 = s3;
}

在这里插入图片描述
分析:

当构造s1时先构造父类Person然后构造Student

拷贝构造s2是先调用Person的拷贝构造再调用Student的拷贝构造

然后构造s3与构造s1相同,先构造父类Person然后构造Student

=也是先调用父类Person然后调用子类Student

最后s3、s2、s1挨个析构,先子后父

并且父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构

五、继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

记住一句话:父亲的朋友不是我的朋友而是我的叔叔

class B;
class A
{
public:
	friend void C(const A& a, const B& b);

protected:
	int _a;
};

class B : public A
{
protected:
	int _b;
};

void C(const A& a,const B& b)
{
	cout << a._a << endl;
	cout << b._b << endl;
}

void test()
{
	A a;
	B b;
	C(a, b);
}

在这里插入图片描述

可以看出C是基类A的友元函数,而C是无法访问派生类B的内容的

六、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,无论派生出多少个子类,都只有一个static成员实例

class A 
{
protected:
	int _a;
public:
	static int _d;
};

int A :: _d = 1;

class B : public A
{
protected:
	int _b;
};

int main()
{
	A a;
	A::_d++;
	B b;
	B::_d++;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七、复杂的菱形继承以及菱形虚拟继承

1、菱形继承

继承分为单继承多继承

单继承:一个子类只有一个直接父类
多继承:一个子类有两个及以上直接父类

有了多继承的继承方式,就会产生一种继承方式叫做菱形继承,这是多继承的一种特殊形式

菱形继承:菱形继承是指一个派生类(孙子类)同时继承自两个直接或间接基类(子类),而这两个基类又都继承自同一个更基础的基类(父类),由于这种继承关系在图形上类似于菱形,因此得名菱形继承

菱形继承会出现一个问题:菱形继承有数据冗余和二义性的问题,也就是说,按照上面的说法,孙子类的对象中会有两份父类对象,多了一份即数据冗余,访问父类时无法确定访问的是两个子类对象的哪一个

class A
{
public:
	int _a;
};

class B : public A
{
protected:
	int _b;
};

class C : public A
{
protected:
	int _c;
};

class D : public B, public C //多继承的方式
{
protected:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 0;
	d.C::_a = 0;

	d._a;
	return 0;
}

继承关系如下图
在这里插入图片描述
在这里插入图片描述
这里可以看到,间接访问的方式就是指定访问哪个父类成员,这样虽然可以解决二义性的问题,但数据冗余仍然存在

这段代码跟上面那段不一样!

class A
{
public:
	int _a;
};

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

class C : 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;
}

这张内存图可以清楚地看到内存分布的现象
在这里插入图片描述
首先,内存是分区的,挨在一起的是一个类的实例化成员,我们看到前两行是B类中的成员_a,_b,中间两行是C类中的成员_a,_c,由于D类没有实例化_a,所以只有一个_d,说到这里我们发现_a有两个,且存储在不同的地方,这就是内存冗余,解决办法之一就是虚拟继承

2、菱形虚拟继承

class A
{
public:
	int _a;
};

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

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;
}

在这里插入图片描述
在这里插入图片描述
通过上面两张调试图可以分析出来:虚拟继承后的父类被孙子类调用时都是同一个,存放在同一块内存当中
在这里插入图片描述
当孙子类调用子类对象时,如果父类成员被实例化,那么存储数据的上方位置会有一个指针,通过解析我们发现这个指针指向的位置存储着一个数据,而这个数据正是存放父类成员的位置地址与这个指针的位置地址的差,也就是说,这个指针存储的是到父类成员地址的距离(偏移量),通过解析这些数据,可以得到父类成员的值

八、继承的总结与反思

建议不要使用菱形继承,难搞

继承和组合

public继承是一种is-a的关系,每个派生类对象都是一个基类对象

组合是一种has-a的关系,B组合A,每个B对象中都有一个A对象

优先使用对象组合,类继承次之,因为类继承的耦合性太强,我们追求低耦合、高内聚,也就是对象之间的联系少,对象内的成员联系紧密,对象组合比起类继承的耦合性低,其中一个改变对另一个的影响较小

继承允许你根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常被称为白箱复用,术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 ,继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响,派生类和基类间的依赖关系很强,耦合度高

对象组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得,对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的,对象只以“黑箱”的形式出现,组合类之间没有很强的依赖关系,耦合度低,优先使用对象组合有助于保持每个类被封装

实际尽量多去用组合,组合的耦合度低,代码维护性好,不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承,类之间的关系可以用继承,可以用组合,就用组合


今日分享就到这了~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(学习记录)使用HAL库 STM32CubeMX——spi(DMA)配置OLED

&#xff08;学习总结&#xff09;STM32CubeMX HAL库教程 学习笔记撰写心得 &#xff08;学习总结&#xff09;STM32CubeMX HAL库教程 学习笔记撰写心得https://blog.csdn.net/Wang2869902214/article/details/142435481 使用HAL库 STM32CubeMX——spi配置 使用硬件spi驱动7针…

element 输入框文字+对应签进行长度 和 的判断

输入文字长度 指定标签的长度 &#xff08;判断长度并提示&#xff09; <div style"position: relative;" classchangyongyu><el-input type"textarea" :autosize"{ minRows: 8, maxRows: 8 }" style"margin-bottom:10px;"…

【React】useEffect

1. 基本介绍 概念 语法 副作用函数依赖项数组&#xff08;空数组时&#xff0c;只会在组件渲染完毕后执行一次副作用函数&#xff09; 2. 使用 import { useEffect, useState } from reactfunction App() {const [value, setValue] useState(0)useEffect(() > {console…

docker-图形化工具-portainer的使用

文章目录 1、安装和启动2、设置登陆密码3、dashboard 上述对容器和镜像的管理都是基于docker客户端的命令来完成&#xff0c;不太方便。为了方便的对docker中的一些对象(镜像、容器、数据卷…)来进行管理&#xff0c;可以使用Portainer来完成。Portainer是一个可视化的容器镜像…

Spring Boot 学习之路 -- Service 层

前言 最近因为业务需要&#xff0c;被拉去研究后端的项目&#xff0c;代码框架基于 Spring Boot&#xff0c;对我来说完全小白&#xff0c;需要重新学习研究…出于个人习惯&#xff0c;会以 Blog 文章的方式做一些记录&#xff0c;文章内容基本来源于「 Spring Boot 从入门到精…

如何帮助我们改造升级原有架构——基于TDengine 平台

一、简介 TDengine 核心是一款高性能、集群开源、云原生的时序数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;&#xff0c;专为物联网IoT平台、工业互联网、电力、IT 运维等场景设计并优化&#xff0c;具有极强的弹性伸缩能力。同时它还带有内建的缓存、…

AI可信度标准与框架

AI 可信度被定义为&#xff1a;无论从技术层面还是社会层面&#xff0c;AI 在执行任务时均能够赢得用户的信任和接受的程度。具体地&#xff0c;一个高可信度的 AI 应包含两个组成部分&#xff0c;这些组成部分应贯穿于系统的整个生命周期&#xff1a; &#xff08;一&#xff…

数据结构 ——— C语言实现动态顺序表

目录 顺序表的概念 顺序表的结构&#xff08;静态顺序表和动态顺序表&#xff09; 1. 静态顺序表&#xff1a;使用固定长度的数组存储元素 2. 动态顺序表&#xff1a;使用动态开辟的数组存储元素 为什么选择实现动态顺序表 静态顺序表的缺点&#xff1a; 动态顺序表的优点…

读构建可扩展分布式系统:方法与实践15可扩展系统的基本要素

1. 可扩展系统的基本要素 1.1. 分布式系统在本质上就是复杂的&#xff0c;你必须考虑多种故障模式&#xff0c;并设计应对所有可能发生的情况的处理方式 1.2. 大规模应用程序需要协调大量的硬件和软件组件&#xff0c;共同实现低延迟和高吞吐量的能力 1.3. 面临的挑战是将所…

入门Django

Django Django 简介URL组成部分详解第一个Django项目创建一个Django项目运行Django项目项目结构介绍project和app的关系 URL与视图函数的映射URL的两种传参方式在URL中携带参数 path函数url路由模块化url反转 Django 简介 Django 是一个高级的 Python Web 框架&#xff0c;用于…

书生白嫖A100活动之——OpenCompass

内容来源&#xff1a;Tutorial/opencompass/readme.md at camp2 InternLM/Tutorial GitHub 概览 在 OpenCompass 中评估一个模型通常包括以下几个阶段&#xff1a;配置 -> 推理 -> 评估 -> 可视化。 配置&#xff1a;这是整个工作流的起点。您需要配置整个评估过…

HTML中的表单(超详细)

一、表单 1.语法 <!-- action&#xff1a;提交的地方 method&#xff1a;提交的方式&#xff08;get会显示&#xff0c;post不会&#xff09; --> <form action"#" method"get"><p>名字&#xff1a;<input name"name" ty…

【Geoserver使用】SRS处理选项

文章目录 前言一、Geoserver的三种SRS处理二、对Bounding Boxes计算的影响总结 前言 今天来看看Geoserver中发布图层时的坐标参考处理这一项。根据Geoserver官方文档&#xff0c;坐标参考系统 (CRS) 定义了地理参考空间数据与地球表面实际位置的关系。CRS 是更通用的模型&…

3. 轴指令(omron 机器自动化控制器)——>MC_MoveFeed

机器自动化控制器——第三章 轴指令 8 MC_MoveFeed变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启运动指令▶多重启动运动指令▶异常 示例程序▶参数设定▶动作示例▶梯形图▶结构文本(ST) MC_MoveFeed 指定自外部输入的中断输入发生位置起的移动距…

2024免费录屏软件的宝藏功能与实用技巧

在手机上操作很多时候为了记录方便都直接截图或者录屏&#xff0c;其实电脑也一样。现在面向电脑的录屏工具纷繁复杂&#xff0c;很容易让我们挑花了眼。今天这篇文章我将介绍几款免费的录屏软件为大家提供参考。 1.福昕录屏大师 链接达达&#xff1a;www.foxitsoftware.cn/R…

深入探索 RUM 与全链路追踪:优化数字体验的利器

作者&#xff1a;梅光辉&#xff08;重彦&#xff09; 背景介绍 随着可观测技术的持续演进&#xff0c;多数企业已广泛采用 APM、Tracing 及 Logging 解决方案&#xff0c;以此强化业务监控能力&#xff0c;尤其在互联网行业&#xff0c;产品的体验直接关系着用户的口碑&…

使用Crawler实例进行网页内容抓取

网页内容抓取的背景 随着互联网的快速发展&#xff0c;网页上的信息量日益庞大。如何从海量的网页中快速、准确地抓取所需信息&#xff0c;成为了一个技术挑战。网页内容抓取技术通过自动化的方式&#xff0c;模拟用户浏览网页的过程&#xff0c;获取网页上的文本、图片、链接…

mybatisplus介绍以及使用(上)

目录 一、概念 1、什么是mybatisplus 2、为什么要使用mybatisplus 二、mybatisplus的使用 1、安装 2、常用注解 3、条件构造器 一、概念 1、什么是mybatisplus MyBatis-Plus&#xff08;简称MP&#xff09;是一个基于MyBatis的增强框架&#xff0c;旨在简化开发、提高…

学习C语言(20)

在这段没有更新的时间作者大一开学了&#xff0c;军训期间一直比较忙没时间学习&#xff0c;9月23号结束了为期十四天的军训&#xff0c;今天开始重新更学习C语言的博客 整理今天的学习内容 1.浮点数在内存中的储存 浮点数包括float&#xff0c;double&#xff0c;long doub…

vue实现左侧数据拖拽到右侧区域,且左侧数据保留且左侧数据不能互相拖拽改变顺序

一、案例效果 二、案例代码 封装左侧抽屉 DrawerSearch.vue <template><div><mtd-form :model"formDrawerSearch" ref"formCustom" inline><mtd-form-item><mtd-inputtype"text"v-model"formDrawerSearch.hos…