【C++】继承(定义、菱形继承、虚拟继承)

news2024/11/17 15:26:14

 🌈个人主页:秦jh_-CSDN博客
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482

 9efbcbc3d25747719da38c01b3fa9b4f.gif​ 

目录

继承的概念

继承定义

定义格式

继承关系和访问限定符 

继承基类成员访问方式的变化 

 基类和派生类对象赋值转换

 继承中的作用域

 派生类的默认成员函数

继承与友元 

继承与静态成员 

复杂的菱形继承及菱形虚拟继承 

虚拟继承解决数据冗余和二义性的原理 

 继承的总结和反思

继承和组合 


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了继承的相关内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。 

继承定义

定义格式

 Person是父类,也称作基类。Student是子类,也称作派生类。

继承关系和访问限定符 

继承基类成员访问方式的变化 

记忆方法:基类的私有成员,无论哪种方式继承,在派生类中都是不可见。基类其他成员在子类的访问方式,取基类成员的访问限定符和继承方式中小的一个。

在继承中,一般用公有和保护,少用私有。

总结: 

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
  5.  在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

 基类和派生类对象赋值转换

 

派生类对象可以赋值给基类对象。

注意:必须是公有继承才可以,保护和私有都不行。 

public继承是is -a的关系,即每个子类对象都是一个特殊的父类对象。

  • 派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。 
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类 的指针是指向派生类对象时才是安全的。

切片有赋值兼容,在赋值的时候不会产生临时对象,就不需要加const。如下图,此时ref是直接指向派生类中基类的那一部分。

 继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员。

 

默认访问子类的,想要访问父类前面就得加上父类的类域。 

B中的fun和A中的fun不是构成重载,因为不是在同一作用域 。

B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

想在子类对象调用父类方法也要加上类域。 

 派生类的默认成员函数

上方中父类有默认构造,子类会调用父类的默认构造。 

上图父类没有默认构造,此时子类如果不显示调用,就会报错。显示调用如下:

  1.  派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认 的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3.  派生类的operator=必须要调用基类的operator=完成基类的复制。
  4.  派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5.  派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。
  7.  因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲 解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

构造是先父后子,析构是先子后父。

成员函数代码:

class Person
{
public:
	Person(const char* name = "peter")
		: _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)  //传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; //学号
};

如果没有写默认成员函数,子类成员的内置类型不做处理,自定义类型会去调用他的默认构造。而父类成员可以看作是一个自定义类型成员,会回去父类找默认构造函数。

继承与友元 

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

继承与静态成员 

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

 

静态成员是共用的,他们的地址都是一样的。 

复杂的菱形继承及菱形虚拟继承 

单继承:一个子类只有一个直接父类时称这个继承关系为单继承 

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 

菱形继承:菱形继承是多继承的一种特殊情况。 


上方虽然显示指定访问哪个父类的成员解决了二义性问题,但是数据冗余问题仍无法解决。这时就需要使用虚拟继承:

此时,_name就只有一份了。

注意:virtual是加在腰部的类的。

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和 Teacher的继承Person时使用虚拟继承,即可解决问题。

虚拟继承解决数据冗余和二义性的原理 

我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型 

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

下图是菱形继承的内存对象成员模型:

下图是菱形虚拟继承的内存对象成员模型:

D对象中将A放到的了对象组成的最下面,这个A同时属于B和C。那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,A叫做虚基类,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

上图也是菱形继承,virtual要放在继承了公共基类的地方。 

 继承的总结和反思

一般不建议设计出多继承,一定不要设 计出菱形继承。否则在复杂度及性能上都有问题。 

继承和组合 

  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 
  • 优先使用对象组合,而不是类继承。
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用。术语“白箱”是相对可视性而言:在继承方式中,基类的 内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

适合is-a关系,就用继承

适合has-a关系,就用组合。

is-a和has-a的关系都可以,就用组合。

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

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

相关文章

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第46课-使用json文件

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第45课-使用头像 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎&…

Java版ERP管理系统源码解析:利用Spring Cloud Alibaba和Spring Boot实现微服务架构

在当今数字化浪潮的推动下&#xff0c;企业对于高效、稳定且易于扩展的管理系统需求日益增长。为了满足这一需求&#xff0c;我们精心打造了一款基于Java技术的鸿鹄ERP&#xff08;Enterprise Resource Planning&#xff09;管理系统。该系统充分利用了Spring Cloud Alibaba、S…

C# Web控件与数据感应之数据返写

目录 关于数据返写 准备视图 范例运行环境 ControlInducingFieldName 方法 设计与实现 如何根据 ID 查找控件 FindControlEx 方法 调用示例 小结 关于数据返写 数据感应也即数据捆绑&#xff0c;是一种动态的&#xff0c;Web控件与数据源之间的交互&#xff0c;数据…

达梦数据守护集群脑裂恢复

集群环境参考上篇 达梦数据守护集群部署 https://blog.csdn.net/qq_25045631/article/details/139900164 集群发散脑裂时&#xff0c;监视器显示如下&#xff0c;实例GRP1_RT_01发生脑裂 1. 关闭DW环境 因为Global守护类型的守护进程&#xff0c;会自动将数据库实例切换到O…

MySQL集群高可用架构之MySQL InnoDB Cluste

今天我将详细的为大家介绍Centos 7.5 基于 MySQL 5.7的 InnoDB Cluster 多节点高可用集群环境部署的相关知识&#xff0c;希望大家能够从中收获多多&#xff01;如有帮助&#xff0c;请点在看、转发支持一波&#xff01;&#xff01;&#xff01; 一、MySQL InnoDB Cluster 介…

【 华为OD机试】信号发射和接收(C++ Java JavaScript Python)

题目 题目描述 有一个二维的天线矩阵,每根天线可以向其他天线发射信号,也能接收其他天线的信号,为了简化起见,我们约定每根天线只能向东和向南发射信号,换言之,每根天线只能接收东向或南向的信号。 每根天线有自己的高度anth,每根天线的高度存储在一个二维数组中,各个天…

Web框架简介

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 如果你要从零开始建立了一些网站&#xff0c;可能会注意到你不得不反复解决一些类似的问题。这样做是令人厌烦的&#xff0c;并且违反了良好编程的核…

【系统架构设计师】四、嵌入式基础知识(软件|软件设计|硬件|式总线逻辑)

目录 一、嵌入式软件 1.1 嵌入式软件分类 1.2 板级支持包(BSP) 1.3 BootLoader 1.4 设备驱动程序 二、嵌入式软件设计 2.1 编码 2.2 交叉编译 2.3 交叉调试 三、嵌入式系统硬件的分类 3.1 根据用途分类 3.2 存储器分类 四、内&#xff08;外&#xff09;总线逻辑 …

苹果Mac系统安装adobe软件“无法打开install因为无法验证开发者”解决方法

对于大部分小伙伴&#xff0c;特别是从事视频后期、设计等专业的人来说&#xff0c;Adobe全家桶系列软件&#xff0c;相信都或多或少用过&#xff0c;比如Photoshop、Premiere、illustrator、Lightroom等等。这些软件不仅支持Windows系统&#xff0c;也完美适配于苹果Mac系统&a…

AcWing算法基础课笔记——动态规划之背包问题

背包问题 1. 01背包问题 解题思路&#xff1a; 题目 2. 01背包问题 - AcWing题库 代码 优化前&#xff1a; #include<iostream> #include<algorithm>using namespace std;const int N 1010;int n, m; int v[N], w[N]; int f[N][N];int main() {cin >> …

旋转机械振动信号特征提取(Python)

前缀 &#xff1a;将一维机械振动信号构造为训练集和测试集&#xff08;Python&#xff09; https://mp.weixin.qq.com/s/DTKjBo6_WAQ7bUPZEdB1TA import pandas as pd import numpy as np import scipy.io as sio import statistics_hamming from statistics_hamming import…

文华财经幅图指标公式大全源码

文华财经幅图指标公式大全源码下载&#xff1a; DIFF:EMA(CLOSE,55) - EMA(CLOSE,89),NODRAW; DEA: EMA(DIFF,9),NODRAW; MACD:2*(DIFF-DEA),NODRAW; DIFF1: EMA(CLOSE,12) - EMA(CLOSE,26),NODRAW; DEA1: EMA(DIFF1,9),NODRAW; MACD1:2*(DIFF1-DEA1),DOT,COLORYELLOW; DRAW…

Java | Leetcode Java题解之第188题买卖股票的最佳时机IV

题目&#xff1a; 题解&#xff1a; class Solution {public int maxProfit(int k, int[] prices) {if (prices.length 0) {return 0;}int n prices.length;k Math.min(k, n / 2);int[] buy new int[k 1];int[] sell new int[k 1];buy[0] -prices[0];sell[0] 0;for (…

【源码下载】宇宙星空

更多精彩内容尽在数字孪生平台&#xff0c;关注公众号【sky的数孪技术】&#xff0c;技术交流、源码下载请添加VX&#xff1a;digital_twin123 概述 用threejs和gsap实现的宇宙星空效果&#xff0c;访问 http://8.130.10.148:3000/digitaltwin/index.html&#xff0c;公众号后…

mybatis x插件的使用教程(详细)

MyBatisX 的主要功能 代码生成&#xff1a; 自动生成 MyBatis 的 Mapper、XML 配置文件和实体类&#xff0c;大大减少手工编写代码的工作量。 智能代码补全&#xff1a; 提供 SQL 语句和 MyBatis 配置的智能代码补全功能&#xff0c;使开发者能够更快地编写代码。 代码导航&…

怎样激励员工积极应用新版FMEA培训后的知识?

在快节奏的职场环境中&#xff0c;新版FMEA&#xff08;失效模式与影响分析&#xff09;的培训无疑是提升员工技能、优化工作流程的重要一环。然而&#xff0c;如何让员工积极地将所学知识应用于实际工作中&#xff0c;却是一个值得深入探讨的问题。下面&#xff0c;深圳天行健…

数据结构 —— 哈夫曼树

数据结构 —— 哈夫曼树 哈夫曼树定义构造算法特性应用 哈夫曼编码核心概念工作原理特点 我们今天来看哈夫曼树&#xff1a; 哈夫曼树 哈夫曼树&#xff08;Huffman Tree&#xff09;&#xff0c;是一种特殊的二叉树&#xff0c;由D.A. Huffman在1952年提出&#xff0c;主要用…

如何选择适合的接口自动化测试工具!

引言&#xff1a; 在现代软件开发中&#xff0c;接口自动化测试已经成为保证软件质量的重要环节。通过自动化测试工具&#xff0c;可以有效地提高测试效率、减少人力成本&#xff0c;并且能够更好地发现和解决潜在的问题。然而&#xff0c;面对众多的接口自动化测试工具&#…

电子设备抗震等级与电子设备震动实验

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139923445 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

机器学习之支持向量机丨神经网络测试

选择题 SVM中的什么是支持向量&#xff1f; 【 正确答案: D】 A. 数据集中的所有样本 B. 模型参数 C. 模型的预测输出 D. 训练数据中离决策边界最近的样本点 支持向量机(SVM)算法的主要目标是&#xff1a; 【 正确答案: C】 A. 最小化间隔 B. 最小化损失函数 C. 最大化间隔 D.…