C++继承与多态—多重继承的那些坑该怎么填
课程总目录
文章目录
- 一、虚基类和虚继承
- 二、菱形继承的问题
一、虚基类和虚继承
虚基类:被虚继承的类,就称为虚基类
virtual作用:
- virtual修饰成员方法是虚函数
- 可以修饰继承方式,是虚继承,被虚继承的类就称为虚基类
注意与抽象类(有纯虚函数的类)区分开来
来看这段代码:
class A
{
public:
private:int ma;
};class B : public A
{
public:
private:int mb;
};
//A a; 4个字节
//B b; 8个字节
使用指令cl xxx.cpp -d1reportSingleClassLayoutA
和cl xxx.cpp -d1reportSingleClassLayoutB
看一下
如果采用虚继承
class B : virtual public A
再来看一下,B从8字节变为了12字节了
分析:当我们遇到虚继承时候,要考虑派生类B的内存布局时,首先我们先不考虑虚继承。类B继承了基类A的ma
,还有自己的mb
;当我们基类A被虚继承后,基类A变为虚基类,虚基类的数据一定要被挪到派生类数据的最后面,再在最前面添加一个vbptr
来看一些例题
class A {};
sizeof(A)=1 //空类大小是1class B : public A {};
sizeof(B) = 1
class A
{virtual void fun() {}
};
sizeof(A)=1class B : public A {};
sizeof(B) = 4 //B的内存里有vfptr
class A
{virtual void fun() {}
};
sizeof(A)=1class B : virtual public A {};
sizeof(B) = 8 //B的内存里有vfptr和vbptr
总结:
vfptr
:一个类有虚函数,这个类生成的对象就有vfptr
,指向vftable
vbptr
:派生类中虚继承基类,会有vbptr
vftable
:存放RTTI指针(指向运行时RTTI信息)、虚函数地址。vbtable
:第一行为向上偏移量,第二行为vbptr
离虚基类数据在派生类内存中的偏移量。
接下来再来看,当虚基类指针与虚函数指针在一起出现的时候会发生什么呢?
class A
{
public:virtual void func() { cout << "call A::func" << endl; }
private:int ma;
};class B : virtual public A
{
public:void func() { cout << "call B::func()" << endl; }
private:int mb;
};int main()
{// 基类指针指向派生类对象,永远指向的是派生类中基类部分数据的起始地址A* p = new B();p->func();delete p;return 0;
}
可以看到,调用是没有被影响到的,但是delete
会出错
分析:
B的内存布局:B首先从A中获取vfptr
与ma
,B中还有自己的mb
此时A被虚继承,从A中继承来的所有的东西都移动到派生类的最后面,然后在最前面补一个vbptr
,vbptr
指向vbtable
,vfptr
指向vftable
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址
普通情况下,派生类内存布局先是基类数据,再是派生类自己的数据,基类指针指向派生类对象时,基类指针指向的就是派生类内存的起始部分。
但是在虚继承下,基类为虚基类,虚基类的数据被挪到派生类最后面,最前面补上vbptr
,此时再用基类指针指向派生类对象时候,基类指针还是指向派生类基类部分数据的起始地址,也即指向vfptr
,这也是能正常调用p->func();
的原因
那么在释放内存的时候呢?现在p
指向的是vfptr
,从vfptr
开始释放内存,,而对象内存现在是从vbptr
开始,这就出错了
验证一下:
class A
{
public:virtual void func() { cout << "call A::func" << endl; }void operator delete(void* p){cout << "operator delete p:" << p << endl;free(p);}
private:int ma;
};class B : virtual public A
{
public:void func() { cout << "call B::func()" << endl; }void* operator new(size_t size){void* p = malloc(size);cout << "operator new p:" << p << endl;return p;}
private:int mb;
};int main()
{// 基类指针指向派生类对象,永远指向的是派生类中基类部分数据的起始地址。A* p = new B();cout << "main p:" << p << endl;p->func();delete p;return 0;
}
operator new p:00D316A0
main p:00D316A8
call B::func()
operator delete p:00D316A8
可以看到,从A0
开始new
的,返回给p
的是A8
,delete
的时候也是A8
,也就是从vfptr
开始释放的,这是不对的
但是,这段代码也能说是错的,这和编译器有关,在Windows
的vs
中,是从vfptr
开始释放的,但是在linux
的g++
下,会自动偏移到new
出来的内存的起始部分来进行释放
如果在栈上开辟内存,基类指针指向派生类对象,出了作用域自己进行析构,不涉及内存的释放,这样是没有问题的,正常运行不会报错
B b;
A *p = &b;
cout << "main p:" << p << endl;
p->func();
运行结果:
main p:010FFE04
call B::func()
使用命令cl xxx.cpp -d1reportSingleClassLayoutB
查看一下
再来看,这时有人会问了,派生类为啥不像下面这样画呢?
如果是这样画,也就是vfptr
属于B的作用域,这是不对的,因为A中有虚函数,vfptr
是从A中继承而来的
如果真的这样画的话,那就是基类中没有虚函数,从派生类中才有的虚函数
二、菱形继承的问题
多重继承:可以复用多个基类的代码到派生类中
但是多重继承中也会出现问题:菱形继承、半圆形继承等
这些都会导致派生类有多份间接基类的数据,此时可以采用虚继承来解决
菱形继承代码:
class A
{
public:A(int data) : ma(data) { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }
protected:int ma;
};
//==========================================================
class B : public A
{
public:B(int data) : A(data), mb(data) { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }
protected:int mb;
};class C : public A
{
public:C(int data) : A(data), mc(data) { cout << "C()" << endl; }~C() { cout << "~C()" << endl; }
protected:int mc;
};
//==========================================================
class D : public B, public C
{
public:D(int data) : B(data), C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }
protected:int md;
};int main()
{D d(10);return 0;
}
运行结果:
A()
B()
A()
C()
D()
~D()
~C()
~A()
~B()
~A()
来看一下D的内存布局
用指令cl xxx.cpp -d1reportSingleClassLayoutD
看看
可以看到调用了两次A的构造,同时数据重复了
怎么解决呢?虚继承
class A { ... };
//==========================================================
class B : virtual public A { ... };
class C : virtual public A { ... };
//==========================================================
class D : public B, public C { ... };
此时内存布局变了,解决了多份数据的问题
用指令cl xxx.cpp -d1reportSingleClassLayoutD
看看
但是注意,此时编译会报错,因为现在A::ma
靠在了D的作用域上面,我们要在D里面给A初始化
class D : public B, public C
{
public:D(int data) : A(data), B(data), C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }
protected:int md;
};
再运行看一看结果:
A()
B()
C()
D()
~D()
~C()
~B()
~A()
多重继承的好处:可以做更多代码的复用,比如上面的例子,D继承自B和C,那么就可以B* p = new D();
或C* p = new D();
,有两个基类,两个基类指针都可以指向派生类对象
相关文章:
C++继承与多态—多重继承的那些坑该怎么填
课程总目录 文章目录 一、虚基类和虚继承二、菱形继承的问题 一、虚基类和虚继承 虚基类:被虚继承的类,就称为虚基类 virtual作用: virtual修饰成员方法是虚函数可以修饰继承方式,是虚继承,被虚继承的类就称为虚基类…...
论文阅读:基于谱分析的全新早停策略
来自JMLR的一篇论文,https://www.jmlr.org/papers/volume24/21-1441/21-1441.pdf 这篇文章试图通过分析模型权重矩阵的频谱来解释模型,并在此基础上提出了一种用于早停的频谱标准。 1,分类难度对权重矩阵谱的影响 1.1 相关研究 在最近针对…...
1.接口测试-postman学习
目录 1.接口相关概念2.接口测试流程3.postman基本使用-创建请求(1)环境(2)新建项目集合Collections(3)新建collection(4)新建模块(5)构建请求请求URLheader设…...
2024年码蹄杯本科院校赛道初赛(省赛)
赛时所写题,简单写一下思路,qwq 第一题: 输出严格次小值, //#pragma GCC optimize(2)#include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <queue> #incl…...
PHP蜜语翻译器在线文字转码解码源码
源码介绍 PHP蜜语翻译器在线文字转码解码源码 文字加密通话、一键转换、蜜语密码 无需数据库,可以将文字、字母、数字、代码、表情、标点符号等内容转换成新的文字形式,通过简单的文字以不同的排列顺序来表达不同的内容!支持在线加密解密 有多种加密展示…...
安卓浏览器区分启动、打开、分享
搞了几个钟头,终于全兼容了,分享有2种类型! void getDataFromIntent(Intent intent) {if (intent.getAction().equals(Intent.ACTION_VIEW)) {urln intent.getDataString();if (urln ! null) {if (urln.contains("\n"))urln url…...
C/C++ 数组负数下标
一 概述 在 C 中,数组是一块连续的内存空间,数组的下标通常用来定位这段内存中的特定元素。下标通常从 0 开始,最大到数组长度减 1。例如,一个有 10 个元素的数组,其有效下标范围是从 0 到 9。 当你尝试使用负数下标来…...
钓鱼网站开发原理(社会工程学)
钓鱼网站开发原理(社会工程学) 一、课程简介1、课程大纲2、课程目标3、知识储备 二、钓鱼网站简介1、什么是钓鱼网站2、开发&原理 三、PHP环境搭建1、简介2、自动安装MySQL/apache/PHP3、安装navicat 四、PDO表单入库案例1、语法2、显示登录表单3、入…...
如何优雅地使用 console.log 打印数组或对象
一、背景 使用 console.log 在控制台中打印数组或者对象时,很多时候它们的字段都是默认关闭的,需要手动一个个的点开,非常不直观且麻烦。 二、解决方案 使用 JSON.stringify() 的第三个参数 我们来看一下官方对于 JSON.stringify 的介绍 三、…...
模式分解的概念(下)-无损连接分解的与保持函数依赖分解的定义和判断、损失分解
一、无损连接分解 1、定义 2、检验一个分解是否是无损连接分解的算法 输入与输出 输入: 关系模式R(U,F),F是最小函数依赖集 R上的一个分解 输出: 判断分解是否为无损连接分解 (1&#x…...
vue3父组件获取子组件的实例对象
一,ref 在父组件的模板里,对子组件的标签定义ref属性,并且设置属性值,在方法里获取ref()获取实例对象。 父组件: <template><div ><div>我是父组件</div><<SonCom ref"sonComRe…...
主流框架选择:React、Angular、Vue的详细比较
目前前端小伙伴经常使用三种广泛使用的开发框架:React、Angular、Vue - 来设计网站 Reactjs:效率和多功能性而闻名 Angularjs:创建复杂的应用程序提供了完整的解决方案,紧凑且易于使用的框架 Vuejs:注重灵活性和可重用…...
交易者的意义是什么?
按照阿德勒的说法:人生的意义就是为社会创造价值,推动整个人类社会的发展进步。 我认同且秉持这种观点。 而在交易中,你是否直接或者间接为社会做贡献了呢?这个还真不好说。 但是做为职业交易者,你的存在价值&#…...
io_uring
转:[译] Linux 异步 I_O 框架 io_uring:基本原理、程序示例与性能压测(2020) 新一代异步IO框架 io_uring | 得物技术 干翻 nio ,王炸 io_uring 来了 !!(图解史上最全&a…...
构建高并发Web应用:基于Gunicorn、Flask和Docker的部署指南
目录 一 理解基础组件 什么是Flask? 什么是Gunicorn? 什么是Docker? 二 环境准备 三 构建Flask应用 创建项目结构 编写Flask应用 app/views.py 四 使用Gunicorn部署Flask应用 配置Gunicorn Gunicorn配置文件 五 使用Docker进行容器化部署 编写Dockerfile 构建…...
【Ruby简单脚本02】双色球系统
# frozen_string_literal: true require date # 生成中奖号码的工具 # 红球 1-32 篮球 1-15 def create_num nums [] 6.times do while true num rand(1..32) unless nums.include?(num) nums << num break end end end blue rand(1..15) nums…...
Netty ByteBuf 使用详解
文章目录 1.概述2. ByteBuf 分类3. 代码实例3.1 常用方法3.1.1 创建ByteBuf3.1.2 写入字节3.1.3 扩容3.1.2.1 扩容实例3.1.2.2 扩容计算新容量代码 3.1.4 读取字节3.1.5 标记回退3.1.6 slice3.1.7 duplicate3.1.8 CompositeByteBuf3.1.9 retain & release3.1.9.1 retain &a…...
怎样去掉卷子上的答案并打印
当面对试卷答案的问题时,一个高效而简单的方法是利用图片编辑软件中的“消除笔”功能。这种方法要求我们首先将试卷拍摄成照片,然后利用该功能轻松擦除答案。尽管这一方法可能需要些许时间和耐心,但它确实为我们提供了一个可行的解决途径。 然…...
海思SS928/SD3403开发笔记1——使用串口调试开发板
该板子使用串口可以调试,下面是win11 调试 该板子步骤 1、给板子接入鼠标、键盘、usb转串口 2、下载SecureCRT,并科学使用 下载地址: 链接:https://pan.baidu.com/s/11dIkZVstvHQUhE8uS1YO0Q 提取码:vinv 3、安装c…...
JSON数据操作艺术
在现代Web开发和数据交换场景中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,扮演着至关重要的角色。它以易于阅读的文本形式存储和传输数据对象,而这些对象的核心便是由属性名(键&…...
如何验证Rust中的字符串变量在超出作用域时自动释放内存?
讲动人的故事,写懂人的代码 在公司内部的Rust培训课上,讲师贾克强比较了 Rust、Java 和 C++ 三种编程语言在变量越过作用域时自动释放堆内存的不同特性。 Rust 通过所有权系统和借用检查,实现了内存安全和自动管理,从而避免了大部分内存泄漏。Rust 自动管理标准库中数据类…...
55.Python pip install 安装失败的一个情况Requirement already satisfied
1.问题 以前使用Pycharm 社区版开发的一个项目,今天使用PyCharm 专业版打开,原项目的虚拟环境从venv更换为.venv,然后重新安装插件。安装时,提示Requirement already satisfied: qt_material in c:\tools\python37\lib\site-packa…...
Axios进阶
目录 axios实例 axios请求配置 拦截器 请求拦截器 响应拦截器 取消请求 axios不仅仅是简单的用基础请求用法的形式向服务器请求数据,一旦请求的端口与次数变多之后,简单的请求用法会有些许麻烦。所以,axios允许我们进行创建axios实例、ax…...
C++ 丑数
描述 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第 n个丑数。 数据范围:0≤𝑛≤20000≤n≤…...
小山菌_代码随想录算法训练营第三十天|122.买卖股票的最佳时机II、55. 跳跃游戏 、45.跳跃游戏II、1005.K次取反后最大化的数组和
122.买卖股票的最佳时机II 文档讲解:代码随想录.买卖股票的最佳时机II 视频讲解:贪心算法也能解决股票问题!LeetCode:122.买卖股票最佳时机II 状态:已完成 代码实现 class Solution { public:int maxProfit(vector<…...
SpringMVC系列七: 手动实现SpringMVC底层机制-上
手动实现SpringMVC底层机制 博客的技术栈分析 🛠️具体实现细节总结 🐟准备工作🍍搭建SpringMVC底层机制开发环境 实现任务阶段一🍍开发ZzwDispatcherServlet🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherSer…...
嵌入式web 服务器boa的编译和移植
编译环境:虚拟机 ubuntu 18.04 目标开发板:飞凌OKA40i-C开发板, Linux3.10 操作系统 开发板本身已经移植了boa服务器,但是在使用过程中发现POST方法传输大文件时对数据量有限制,超过1M字节就无法传输,这是…...
什么是js?特点是什么?组成部分?
Js是一种直译式脚本语言,一种动态类型,弱类型,基于原型的高级语言。 直译式:js程序运行过程中直接编译成机器语言。 脚本语言:在程序运行过程中逐行进行解释说明,不需要预编译。 动态类型:js…...
Java 面试题:如何保证集合是线程安全的? ConcurrentHashMap 如何实现高效地线程安全?
在多线程编程中,保证集合的线程安全是一个常见而又重要的问题。线程安全意味着多个线程可以同时访问集合而不会导致数据不一致或程序崩溃。在 Java 中,确保集合线程安全的方法有多种,包括使用同步包装类、锁机制以及并发集合类。 最简单的方法…...
打工人的PPT救星来了!用这款AI工具,10秒生成您的专属PPT
今天帮同事解决了一个代码合并的问题。其实问题不复杂,要把1的代码合到2的位置: 这个处理方式其实很简单,使用 “git cherry-pick hash值” 就可以。 同事直接对我赞许有加,不曾想被领导看到了,对我说了一句ÿ…...
GIT 合拼
合拼有多种方式: 1)合拼分支: git merge [source-branch] 2)合拼提交 : git cherry-pick [commit-hash] 3)合拼单个文件: git checkout [source-branch] – [file] 以上合拼,比如将分…...
利用 Python 和 AI 技术制作智能问答机器人
利用 Python 和 AI 技术制作智能问答机器人 引言 在人工智能的浪潮下,智能问答机器人成为了一种非常实用的技术。它们能够处理大量的查询,提供即时的反馈,并且可以通过机器学习技术不断优化自身的性能。本文将介绍如何使用 Python 来开发一…...
electron系列(一)调用dll
用electron的目的,其实很简单。就是web架构要直接使用前端电脑的资源,但是浏览器限制了使用,所以用electron来达到这个目的。其中调用dll是一个非常基本的操作。 安装 ffi-napi 和 ref-napi 包: npm install ffi-napi ref-napi main.js&…...
VUE3实现个人网站模板源码
文章目录 1.设计来源1.1 网站首页页面1.2 个人工具页面1.3 个人日志页面1.4 个人相册页面1.5 给我留言页面 2.效果和源码2.1 动态效果2.2 目录结构 源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址࿱…...
C语言 | Leetcode C语言题解之第162题寻找峰值
题目: 题解: int findPeakElement(int* nums, int numsSize) {int ls_max0;for(int i1;i<numsSize;i){if(nums[ls_max]>nums[i]);else{ls_maxi;}}return ls_max; }...
利用pickle保存和加载对象
使用 pickle.dump 保存下来的文件可以使用 pickle.load 打开和读取。以下是一个示例,展示了如何使用 pickle 模块保存和加载对象: 保存对象 import pickle# 假设有一个对象 obj obj {"key": "value"}# 将对象保存到文件 with ope…...
定制汽车霍尔传感器
磁电效应霍尔传感器、饱和霍尔传感器、非线性霍尔传感器 霍尔传感器原理 霍尔传感器的工作原理基于霍尔效应,即当一块通有电流的金属或半导体薄片垂直地放在磁场中时,薄片的两端会产生电位差。这种现象称为霍尔效应,两端具有的电位差值称为…...
【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的巡演(100分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 …...
ChatGPT 简介
ChatGPT 是一种基于大型语言模型的对话系统,由 OpenAI 开发。它的核心是一个深度学习模型,使用了 GPT(Generative Pre-trained Transformer)架构。以下是 ChatGPT 的原理和工作机制的详细介绍: ### GPT 架构 1. **Tr…...
大数据实训室建设可行性报告
一、建设大数据实训室的背景与意义 随着信息技术的飞速发展,大数据已成为推动社会进步和经济发展的重要力量。中高职院校作为技能型人才培养的摇篮,承担着为社会输送大数据领域高素质、高技能人才的重要任务。因此,建设大数据实训室…...
学懂C#编程:让函数返回 多个返回值 的几种常用技术
1. 使用 out 或 ref 参数 out 和 ref 参数允许方法修改传入变量的值,并通过它们“返回”多个值。ref 需要变量事先初始化,而 out 不要求。 public void GetValues(out int val1, out string val2) {val1 10;val2 "Hello"; }// 使用示例 int…...
蔚来汽车AI算法工程师,如何理解注意力?
大家好啊,我是董董灿。 今天分享一个上海蔚来汽车的AI算法岗位面试经验总结帖,面试岗位为算法工程师。 这次面试提到的问题,除了与实习相关内容和反问之外,面试官总共问了8个问题,主要集中在深度学习基础概念的理解上…...
信创适配评测
概叙 信创科普参考:全面国产化之路-信创-CSDN博客 有必要再解释一下两个名词“28N”,“79号文件”,因为“28N”指定了由政府牵头从各领域开启国产化的基调,而“79号文件”则指定了国产化的截止日期2027年。 信创的本质是实现中国信…...
【Qt6.3 基础教程 04】探索Qt项目结构和配置文件
文章目录 前言Qt项目的基本结构配置文件:.pro文件基本构成示例.pro文件: qmake和构建过程步骤简述: 修改项目设置结论 前言 当你开始使用Qt进行开发时,理解项目结构和配置文件的作用是至关重要的。这篇博文将带你深入了解Qt项目的…...
SpringBoot测试实践
测试按照粒度可分为3层: 单元测试:单元测试(Unit Testing)又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中…...
Flask-OAuthlib
Flask-OAuthlib库教程 Flask-OAuthlib 是一个为 Flask 应用提供 OAuth1 和 OAuth2 支持的库。它允许开发者轻松地集成第三方 OAuth 服务,或者构建自己的 OAuth 提供者服务。 官方文档链接 Flask-OAuthlib官方文档 架构概述 Flask-OAuthlib 的主要组件包括&…...
树和森林.
目录 一、树 1.1树的存储结构 1.1.1双亲表示法 1.1.2孩子链表 1.1.3孩子兄弟表示法 1.2树与二叉树的转换 1.2.1将树转换成二叉树: 1.2.2将二叉树转换成树 二、森林 2.1森林与二叉树的转换 2.1.1将森林转换成二叉树 2.1.2二叉树转换成森林 三、树和森林的…...
ubuntu下同时安装和使用不同版本的库 librealsense
apt 安装的最新版本在/usr 源码安装的旧版本在/usr/local set(realsense2_DIR /usr/local/) find_package(realsense2 2.50.0 REQUIRED) message( "\n\n ${realsense2_INCLUDE_DIR} ${realsense2_VERSION} RealSense SDK 2.0 is FINDINGING, please install it from…...
openEuler操作系统下静默安装Oracle19c
在openEuler-23.09上安装Oracle19c,创建非容器数据库实例(含静默安装) 操作系统版本 openEuler-23.09-x86_64-dvd.iso ,安装步骤此处省略。。。 最常用且直接的方法来查看openEuler的版本号是查看/etc/os-release文件 [root@openEuler ~]$ cat /etc/os-release NAME="…...
Linux CPU常见命令行详解
在Linux系统中,命令行是管理和监控系统资源的重要工具。特别是当我们需要了解CPU的状态、性能和利用率时,一系列命令行工具就显得尤为重要。本文将详细介绍Linux中与CPU相关的常见命令行工具及其使用方法,帮助大家更好地理解和利用这些工具来…...