当前位置: 首页 > article >正文

C++:继承+菱形虚拟继承的一箭双雕

目录

一、继承概念与定义

1.1、什么是继承?

1.2、继承定义

二、继承关系与访问限定符

2.1、继承方式

三、基类与派生类对象的赋值转换

3.1、向上转型

3.2、对象切片

四、继承中的作用域

4.1、隐藏

五、派生类中的成员函数

5.1、构造与析构

六、继承与友元、静态成员

6.1、友元关系不可继承

6.2、静态成员共享

七、复杂的菱形继承与虚拟继承

7.1、菱形继承问题

7.2、虚拟继承

7.2.1、解决原理

八、继承与组合:如何选择?

1. 继承("is-a"关系)

2. 组合("has-a"关系)

3. 选择原则


一、继承概念与定义

1.1、什么是继承?

简单来说:继承就像现实中的父子关系。孩子继承父母的特征(如眼睛),但孩子也可以有自己的独特属性(如头发)。

而在代码中,继承是子类继承父类的字段和方法,同时可以添加新功能或覆盖父类的方法。

继承是面向对象编程中的一个核心机制,它允许一个类(子类/派生类)基于另一个类(父类/基类)来定义,从而自动获取父类的属性和方法,并可以在此基础上进行扩展或修改。

继承优势?

避免重复编写相同的代码。父类的通用逻辑只需要写一次,子类可以直接继承。减少了代码量。

比如:

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; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

上面代码可以看出对于“老师”和“学生”来说都具有“人”的属性,所以这里Student和Teacher都继承了Person这个类,继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。可以通过调试窗口看出来:

1.2、继承定义

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


二、继承关系与访问限定符

2.1、继承方式

C++支持三种继承方式:public、protected、private,它们决定了基类成员在派生类中的可见性:

基类成员访问权限public继承                        protected继承private继承
基类的public成员派生类的public成员派生类的protected 成员派生类的private 成员
基类的protected 成员派生类的protected 成员派生类的protected 成员派生类的private 成员
基类的private成 员在派生类中不可见在派生类中不可见在派生类中不可见

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

比如:

class Base 
{
public:int a;
protected:int b;
private:int c;
};class Derived : private Base 
{ // a → private, b → private, c不可见(就是不能访问父类的成员)
};

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。

3. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。 


三、基类与派生类对象的赋值转换

3.1、向上转型

派生类对象可以直接赋值给基类的对象、基类指针或引用。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

int main()
{Student sobj ;Person pobj = sobj; // 合法Person* pp = &sobj; // 合法Person& rp = sobj; // 合法sobj = pobj;//非法// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobjStudent* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;return 0;
}

3.2、对象切片

向上转型有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

当派生类对象直接赋值给基类对象时,会发生“切片”——派生类独有的成员被丢弃:


四、继承中的作用域

4.1、隐藏

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

比如:


五、派生类中的成员函数

5.1、构造与析构

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

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

可以看到:派生类对象初始化先调用基类构造再调派生类构造。派生类对象析构清理先调用派生类析构再调基类的析构

注意:虽然这里的子类和父类析构名不同,也没加virtual,看起来不构成隐藏。实际上由于多态的原因析构函数的函数名被特殊处理了,统一处理成destructor。

另外:

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

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


六、继承与友元、静态成员

6.1、友元关系不可继承

基类的友元函数/类不会自动成为派生类的友元。

class Base 
{friend void friendFunc();
};class Derived : public Base 
{// friendFunc() 不是Derived的友元!
};

6.2、静态成员共享

基类的静态成员在整个继承体系中共享。

class Base 
{
public:static int count;
};class Derived : public Base {};int Base::count = 0;// 无论通过Base还是Derived访问,count都是同一个
Base::count++;
Derived::count++; 

七、复杂的菱形继承与虚拟继承

7.1、菱形继承问题

当派生类继承自两个具有共同基类的类时,会导致数据冗余和二义性:在Assistant的对象中Person成员会有两份。

class Person
{
public :string _name ; // 姓名
};class Student : public Person{};class Teacher : public Person{};class Assistant : public Student, public Teacher
{
protected :string _majorCourse ; // 主修课程
};void Test ()
{Assistant a ;// 这样会有二义性无法明确知道访问的是哪一个a._name = "peter"; // 这里的_name指的是Student里的还是Teacher里的?
}

7.2、虚拟继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。

class Person
{
public:string _name; // 姓名
};class Student : virtual public Person {};class Teacher : virtual public Person {};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};void Test()
{Assistant a;a._name = "peter"; //指向唯一的_name
}

7.2.1、解决原理

这里以下面举例:

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

图上用的32位系统展示,所以指针大小4个字节。

原理:通过虚拟继承,D对象仅包含一份A实例,B和C共享这一份。这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。


八、继承与组合:如何选择?

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

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

1. 继承("is-a"关系)

  • 适用场景:派生类是基类的特殊类型(如 Dog 是 Animal)。

  • 优点:代码复用、多态支持。

  • 缺点:可能导致紧耦合。

2. 组合("has-a"关系)

  • 适用场景:类包含另一个类的功能(如 Car 有一个 Engine)。

  • 优点:降低耦合、灵活替换组件

class Engine 
{
public:void start() { /* ... */ }
};class Car 
{
private:Engine engine;  // 组合
public:void start() { engine.start(); }
};

3. 选择原则

  • 优先使用组合:除非需要多态或严格的“is-a”关系。

  • 避免过度继承:继承层次过深会增加复杂度。

相关文章:

C++:继承+菱形虚拟继承的一箭双雕

目录 一、继承概念与定义 1.1、什么是继承&#xff1f; 1.2、继承定义 二、继承关系与访问限定符 2.1、继承方式 三、基类与派生类对象的赋值转换 3.1、向上转型 3.2、对象切片 四、继承中的作用域 4.1、隐藏 五、派生类中的成员函数 5.1、构造与析构 六、继承与友…...

网络:华为数通HCIA学习:静态路由基础

文章目录 前言静态路由基础静态路由应用场景 静态路由配置静态路由在串行网络的配置静态路由在以太网中的配置 负载分担配置验证 路由备份&#xff08;浮动静态路由&#xff09;配置验证 缺省路由配置验证 总结 华为HCIA 基础实验&#xff0d;静态路由 & eNSP静态路由 基础…...

CFResNet鸟类识别:原网络基础上改进算法

​本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊​ 先放一张ResNet50模型的鸟类识别结果图 一 ResNetSE-NetBN import matplotlib.pyplot as plt import tensorflow as tf import warnings as w w.filterwarnings(ignore) # 支持中文 plt.rcP…...

C++ | 文件读写(ofstream/ifstream/fstream)

一、C文件操作核心类 C标准库通过<fstream>提供了强大的文件操作支持&#xff0c;主要包含三个关键类&#xff1a; 类名描述典型用途ofstream输出文件流&#xff08;Output File Stream&#xff09;文件写入操作ifstream输入文件流&#xff08;Input File Stream&#…...

11_常用函数

文章目录 一、概述二、字符函数2.1、获取字符串所占字节数2.2、获取字符个数2.3、拼接字符串2.4、大小写转换2.5、获取子串2.6、获取子串第一次出现的索引2.7、去除字符串前后子字符串2.7.1、去掉左侧空格2.7.2、去掉右侧空格 2.8、左右填充2.9、字符串替换 三、数学函数3.1、四…...

Android穿山甲banner广告穿插到项目的banner中

Android穿山甲banner广告穿插到项目的banner中 项目中的banner需要用第三库的banner&#xff0c;目前是在下面的banner库测试可以 implementation io.github.youth5201314:banner:2.2.2用自己写的banner会显示不了穿山甲banner的&#xff0c;我也不知道为什么。 给下banner加…...

Ubuntu 20.04 出现问号图标且无法联网 修复

在 Ubuntu 中遇到网络连接问题&#xff08;如出现问号图标且无法联网&#xff09;&#xff0c;可以通过以下命令尝试重启网络服务&#xff1a; 1. 推荐先修改DNS 编辑 -> 虚拟机网络编辑器-> VMnet8 ->NAT 设置 -> DNS 设置 -> 设置DNS 服务器 DNS填什么 取决…...

基于Contiue来阅读open-r1中的GRPO训练代码

原创 快乐王子HP 快乐王子AI说 2025年04月03日 23:54 广东 前面安装了vscode[1]同时也安装了Coninue的相关插件[2]&#xff0c;现在想用它们来阅读一下open-r1项目的代码[3]。 首先&#xff0c;从启动训练开始(以GRPO为例子&#xff09; 第一步&#xff0c;使用TRL的vLLM后端…...

51c嵌入式~单片机~合集7~※

我自己的原文哦~ https://blog.51cto.com/whaosoft/13692314 一、芯片工作的心脏--晶振 在振荡器中采用一个特殊的元件——石英晶体&#xff0c;它可以产生频率高度稳定的交流信号&#xff0c;这种采用石英晶体的振荡器称为晶体振荡器&#xff0c;简称晶振。 制作方法 …...

GRPO训练下的参考模型选择

一、普通全量微调模型 核心机制&#xff1a;模型克隆 深拷贝创建 通过create_reference_model(model)对当前模型进行完全复制&#xff08;包括所有层和参数&#xff09;。示例代码&#xff1a;import copy def create_reference_model(model):ref_model copy.deepcopy(model)…...

英菲克(INPHIC)A9无线蓝牙鼠标 链接电脑的方式

英菲克&#xff08;INPHIC&#xff09;A9鼠标链接至电脑时&#xff0c;要长按住“模式切换MODE”按钮5秒左右的时间&#xff0c;此时模式指示灯变成蓝色&#xff0c;并且闪烁。 这时使用电脑的蓝牙设置中&#xff0c;“添加设备”&#xff0c;会出现BT4.0 Mouse提示&#xff0…...

lua表table和JSON字符串互转

--print("local ssxc{\n"..string.gsub(str,":","").."\n}") Utils {} ---------------------------------------------------------------------------------- -- Lua-Table 与 string 转换 local function value2string(value, isA…...

linux命令-find指令

1.文件名和路径 参数 说明 示例 -name pattern 按文件名匹配&#xff08;区分大小写&#xff09; -iname pattern 按文件名匹配&#xff08;忽略大小写&#xff09; -path pattern 按路径匹配 -ipath pattern 按路径匹配&#xff08;忽略大小写&#xff09; find . -name &…...

【每日一个知识点】分布式数据湖与实时计算

在现代数据架构中&#xff0c;分布式数据湖&#xff08;Distributed Data Lake&#xff09; 结合 实时计算&#xff08;Real-time Computing&#xff09; 已成为大数据处理的核心模式。数据湖用于存储海量的结构化和非结构化数据&#xff0c;而实时计算则确保数据能够被迅速处理…...

【3.软件工程】3.5 V开发模型

V模型深度解析&#xff1a;测试驱动的软件开发框架 ⚙️ 一、V模型全景流程图 #mermaid-svg-IoovYFLLXyzJAePg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-IoovYFLLXyzJAePg .error-icon{fill:#552222;}#mermai…...

生成对抗网络(GAN)详解(代码实现)

GANs 的基本概念 This framework can yield specific training algorithms for many kinds of model and optimization algorithm. In this article, we explore the special case when the generative model generates samples by passing random noise through a multilayer …...

leecode第18天

3274.检查棋盘方格颜色是否相同 # 给你两个字符串 coordinate1 和 coordinate2&#xff0c;代表 8 x 8 国际象棋棋盘上的两个方格的坐标。# 以下是棋盘的参考图。 class Solution:"""该类用于检查两个棋盘格子的颜色是否相同"""def checkTwoChe…...

c语言数据结构--------拓扑排序和逆拓扑排序(Kahn算法和DFS算法实现)

#include <stdio.h> #include <string.h> #include <stdbool.h> #include <stdlib.h>//使用卡恩算法(Kahn)和深度优先算法(DFS)实现//拓扑排序和逆拓扑排序//拓扑排序和逆拓扑排序顶点顺序相反//图&#xff0c;邻接矩阵存储 #define MaxVertexNum 100 …...

谷粒微服务高级篇学习笔记整理---nginx搭建正反向代理

正向与反向代理 **正向代理:**客户端向代理服务器发请求并指定目标服务器,代理向目标转交请求并将获得的内容转给客户端。 反向代理:用户直接访问反向代理服务器就可以获得目标服务器的资源。反向代理服务器统一了访问入口。 给首页配置反向代理 修改windows的hosts文件配…...

2.pycharm保姆级安装教程

一、pycharm安装 1.官网上下载好好软&#xff0c;双击打开 2.下一步 3.修改路径地址 (默认也可以) 4.打勾 5.安装 不用重启电脑 二、添加解释器 1.双击软件&#xff0c;打开 2.projects – new project 3.指定项目名字&#xff0c;项目保存地址&#xff0c;解释器 4.右击 – …...

基于方法分类的无监督图像去雾论文

在之前的博客中&#xff0c;我从研究动机的角度对无监督图像去雾论文进行了分类&#xff0c;而现在我打算根据论文中提出的方法进行新的分类。 1. 基于对比学习的方法 2022年 论文《UCL-Dehaze: Towards Real-world Image Dehazing via Unsupervised Contrastive Learning》&a…...

【SQL】取消sql某一列的唯一值key值的方法

在插入数据到sql时&#xff0c;遇到了这个问题&#xff1a; Duplicate entry ‘XXX’ for key 起因是&#xff1a; 我之前设计表的时候&#xff0c;手动给product_title 这个列加了一个key&#xff0c; key 是这个字段的唯一键约束&#xff0c;就不能重复在这一列存入重复的数…...

数据库--SQL

SQL&#xff1a;Structured Query Language&#xff0c;结构化查询语言 SQL是用于管理关系型数据库并对其中的数据进行一系列操作&#xff08;包括数据插入、查询、修改删除&#xff09;的一种语言 分类&#xff1a;数据定义语言DDL、数据操纵语言DML、数据控制语言DCL、事务处…...

SQL语句(一)—— DDL

目录 一、SQL 基础知识 &#xff08;一&#xff09;SQL 通用语法 &#xff08;二&#xff09;SQL 分类 二、DDL —— 数据库操作 1、查询所有数据库 2、查询当前数据库 3、创建数据库 4、删除数据库 5、切换数据库 三、DDL —— 表操作 &#xff08;一&#xff09;查…...

硬件负载均衡:让服务像“牛顿钟”一样稳!

硬件负载均衡:让服务像“牛顿钟”一样稳! 大家好,我是 Echo_Wish,今天要聊聊提高服务可用性的一大利器——硬件负载均衡。如果你是运维领域的一员,肯定对“负载均衡”这个词耳熟能详。然而,很多朋友一提到硬件负载均衡,脑袋可能就卡住了:这是啥?跟软件负载均衡有啥区…...

Husky目标跟踪

1.0设备清单 幻影峡谷、适配器 摄像头及数据线、显卡欺骗器 外接屏幕、键盘鼠标 Husky底盘、便携显示屏、键盘鼠标 移动电源 1.1连线 插排——移动电源幻影峡谷——适配器——插排摄像头——幻影峡谷&#xff08;摄像头固定在机械臂前方的底盘上&#xff09;键盘鼠标显示器…...

高通camx IOVA内存不足,导致10-15x持续拍照后,点击拍照键定屏无反应,过一会相机闪退

定屏闪退问题分析思路&#xff1a; 定屏问题如果是相机问题&#xff0c;一般会出现返帧&#xff0c;导致预览卡死。当然还有其他情况&#xff0c;我们先看返帧情况&#xff0c;发现request和result开始都正常&#xff0c;到12:53:05.443038就没有返帧了&#xff0c;定屏了。往…...

Python----机器学习(线性回归:自求导的方法实现)

一、线性回归方程 目标&#xff1a; 线性回归的目标是找到最佳的系数来使模型与观察到的数据尽可能拟合。 应用&#xff1a; 预测&#xff1a;给定自变量的值&#xff0c;预测因变量的值。 回归分析&#xff1a;确定自变量对因变量的影响程度 线性回归是统计学和机器学习中最简…...

Parasoft C++Test软件单元测试_操作指南

系列文章目录 Parasoft C++Test软件静态分析:操作指南(编码规范、质量度量)、常见问题及处理 Parasoft C++Test软件单元测试:操作指南、实例讲解、常见问题及处理 Parasoft C++Test软件集成测试:操作指南、实例讲解、常见问题及处理 进阶扩展:自动生成静态分析文档、自动…...

QT之QML(简单示例)

需求一&#xff1a;点击按钮弹出菜单&#xff0c;并且自定义菜单弹出位置。 mouse.x 和 mouse.y 获取的是相对于 MouseArea&#xff08;在这个例子中是 Button&#xff09;左上角的局部坐标。如果你想要在鼠标点击位置显示 Menu&#xff0c;你需要将这个局部坐标转换为相对于应…...