C++基类和派生类的内存分配,多态的实现

目录

    • 基类和派生类的内存分配
    • 基类和派生类的成员归属
    • 多态的实现

基类和派生类的内存分配

类包括成员变量(data member)和成员函数(member function)。
成员变量分为静态数据(static data)和非静态数据(non-static data),成员函数分为静态成员函数(static function)、非静态成员函数(non-static function)和虚拟成员函数(vritual function)。

C++编译器将类的静态数据、静态成员函数以及非静态成员函数存储在类对象存储空间之外,并且无论该类声明了多少对象,在内存中只存有一份。
虚拟成员函数也存储在类对象存储空间之外,编译器为每个虚拟成员函数产生一个指针,并将这些指针存储在一个被称为虚基表的表格中。

类对象的存储空间中包括:非静态数据以及指向虚基表的指针。
看个例子,为了方便观察做个字节对齐。

//4字节对齐
#pragma pack(push, 4)class C
{int age;static int year;//静态成员变量,不占用类的空间
public:C(){age = 12;printf("C()\n");}~C() = default;
//    virtual ~C() = default;//定义了虚函数,则是多态类,会生成虚函数地址表void TestFunc(){printf("age=%d\n",age);}
};class D : public C
{int price;
public:D(){price = 2000;printf("D()\n");}void TestFunc(){printf("price=%d\n",price);}
};
#pragma pack(pop)//测试调用D dd;printf("sizeof(C)=%ld\n",sizeof(C));printf("sizeof(D)=%ld\n",sizeof(D));

打印

sizeof(C)=4
sizeof(D)=8

观察一:非多态类
类C的析构函数不是虚函数,此时C和D都不是多态类,也就是普通的类。类的大小就是非静态成员变量的大小之和。
观察D dd的内存分配:

dd	@0x7fffffffe388	D[C]	@0x7fffffffe388	Cage	12	intyear	<optimized out>	price	2000	int

观察dd占用的内存,共8字节,前4字节是0c,转换成十进制是12,也就是基类C中age的大小;后4字节转换成十进制是2000,也就是派生类D中price的大小。

0c 00 00 00 d0 07 00 00

观察二:多态类
把上面例子基类C的虚函数定义为virtual虚函数,则C和D是多态类,打印:

sizeof(C)=12
sizeof(D)=16

C和D的大小分别比非多态类大了8字节,多的8字节其实是指向虚函数表的指针,看下面,比上面多了个vptr。

dd	@0x7fffffffe380	D[C]	@0x7fffffffe380	C[vptr]	_vptr.C	 age	12	intyear	<optimized out>	price	2000	int

观察dd占用的内存,共16字节,前8字节是指向虚函数表的指针;后8字节的前4字节转换10进制是12,也就是基类C中age的大小,后4字节转换成十进制是2000,也就是派生类D中price的大小。

50 d9 55 55 55 55 00 00 0c 00 00 00 d0 07 00 00

一个总结
1、一个类的对象所占用的空间大小:非静态成员变量之和,多态类再加上指向虚基表的指针大小。
2、静态变量year是全局变量被优化,不占用类的大小。
3、类D的对象dd,和基类C的指针地址一样。
4、多态类占用空间比非多态类大8字节,多的8字节其实是指向虚函数表的指针[vptr]。
5、创建一个派生类对象时,先执行基类的构造,再执行派生类的构造,因此内存分配中,前面是基类的非静态成员变量,后面是派生类新增的非静态成员变量。
6、虚函数表指针是在基类构造时创建的,属于基类的一个成员,但派生类也可以访问。

一个多态派生类的对象所占用的内存空间:
在这里插入图片描述

基类和派生类的成员归属

访问范围
1、保护成员的可访问范围比私有成员大,比共有成员小。能访问私有成员的地方都能访问保护成员。
2、基类的私有成员只能在基类访问,派生类不能访问。
3、基类的保护成员可以在派生类的成员函数访问。
4、私有成员只能在类的成员函数访问,这和普通类的定义一致。

覆盖和扩充
1、派生类是对基类进行扩充和修改得到的,基类的所有成员自动成为派生类的成员(私有成员除外)。
2、所谓扩充,指的是派生类中可以添加新的成员变量和成员函数。
3、所谓覆盖,指的是派生类中可以重写从基类继承得到的成员。

一个总结
1、构造与析构顺序:构造时先执行基类的构造函数,再执行派生类的构造函数;析构时先执行派生类的析构函数,再执行基类的构造函数。
2、基类的私有成员,不能在派生类的成员函数访问。
3、基类的保护成员,可以在派生类的成员函数中访问。
4、派生类可以定义和基类中同名的成员变量和非虚成员函数,比如例中的age,基类内存中有个age,派生类新增成员内存中也有一个age,这两个成员变量没有联系。
5、派生类成员函数访问基类非私有成员,可以使用基类::访问。
6、基类的析构函数要定义为虚函数,否则在释放基类指针时不会执行派生类的析构函数,造成隐式的内存泄漏。
7、非多态情况下,派生类和基类是包含和被包含的关系,派生类包含了基类,因此派生类指针可以转换为基类指针,但基类指针不能转换为派生类指针(‘A’ is not polymorphic)。
8、多态情况下,基类和派生类指针可以相互转换,但要关注转换后指针是否有效,可以使用dynamic_cast转换,返回nullptr则转换失败。


//4字节对齐
#pragma pack(push, 4)
class A //基类
{
private:int price;//私有成员,只能在基类的成员函数访问
protected:int age;//保护成员,可以在派生类的成员函数中访问
public:char name[20]= "chw";//公有成员,可以在任何地方访问A(){price = 2000;age = 17;printf("A()\n");}virtual ~A(){printf("~A()\n");}void TestFunc(){printf("price=%d\n",price);printf("age=%d\n",age);printf("name=%s\n",name);}virtual void PrintThis(){printf("A=%p\n",this);}
};class B : public A  //派生类
{
private:int age;//派生类中可以重写从基类继承得到的成员char addr[20];//派生类可以扩充新的成员变量
public:B(){age = 27;printf("B()\n");}~B(){printf("~B()\n");}//覆盖了基类的同名成员函数void TestFunc(){//不能访问基类的私有成员
//        printf("price=%d\n",price);// error: 'price' is a private member of 'A'//可以访问基类的保护成员和公有成员printf("age=%d\n",age);printf("name=%s\n",name);printf("A::age=%d\n",A::age);//基类成员被派生类覆盖,可以使用A::访问基类的成员
//        A::TestFunc();//使用A::也可以访问基类的同名成员函数printf("B=%p\n",this);A::PrintThis();}
};//测试调用B* bb = new B;bb->TestFunc();printf("**********分割线***********\n");A* bb_a = dynamic_cast<A*>(bb);bb_a->TestFunc();printf("sizeof(A)=%ld\n",sizeof(A));printf("sizeof(B)=%ld\n",sizeof(B));delete bb;//基类不能转换为派生类,因为类A没有虚函数,不是多态的//如果类A成员函数TestFunc定义为virtual的,可以转换,但转换完成后aa_b==nullptr,不能使用
//    A* aa = new A;
//    B* aa_b = dynamic_cast<B*>(aa);//error: 'A' is not polymorphic
#pragma pack(pop)

打印

A()
B()
age=27
name=chw
A::age=17
B=0x5555559e15b0
A=0x5555559e15b0
**********分割线***********
price=2000
age=17
name=chw
sizeof(A)=36
sizeof(B)=60
~B()
~A()

内存占用

bb	@0x5555559e15b0	B[A]	@0x5555559e15b0	A[vptr]	_vptr.A	 age	17	intname	"chw\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"	char[20]price	2000	intaddr	"nj\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"	char[20]age	27	int

打印分析:
1、派生类和基类指针地址是一样的(0x5555559e15b0)。
2、派生类重新定义了age,和基类的age是两个没有联系的变量。
3、sizeof(A) = 虚函数表指针(8字节) + price(4字节) + age(4字节) + name(20字节) = 36。
4、sizeof(B) = sizeof(A) + age(4字节) + addr(20字节) = 60。

多态的实现

多态的介绍参考:https://blog.csdn.net/weixin_40355471/article/details/124368317#_844。

通过基类指针或基类引用实现多态
1、对于普通函数,不用管指针是指向基类还是派生类,只和指针变量的数据类型相关,即定义指针变量时的指针数据类型,如果是基类,则始终调用基类的普通函数,如果是派生类,则始终调用派生类的普通函数。
2、对于虚函数,要看基类指针当前指向的是基类还是派生类,如果指向基类则调用基类的虚函数,如果指向派生类则调用派生类的虚函数。
3、派生类指针可以赋值给基类指针,但基类指针赋值给派生类指针时要注意转换的有效性,通常使用dynamic_cast转换,失败时返回nullptr。
4、因此通常使用基类指针或引用,根据基类指针是指向基类还是派生类,实现多态。

class A
{
public:void out1()//普通函数{printf("A(out1)\n");};virtual ~A(){};virtual void out2()//虚函数{printf("A(out2)\n");}
};class B:public A
{
public:virtual ~B(){};void out1(){printf("B(out1)\n");}void out2(){printf("B(out2)\n");}
};//测试调用A *aa = new A;//基类指针,无论aa后面指向基类还是派生类,普通函数都是调用基类的普通函数B *bb = new B;//派生类指针aa->out1();//A(out1)aa->out2();//A(out2)bb->out1();//B(out1)bb->out2();//B(out2)aa = bb;//派生类指针赋值给基类指针bb = dynamic_cast<B*>(aa);//基类指针可以转换成派生类指针,转换失败时返回nullptraa->out1();//A(out1)aa->out2();//B(out2)bb->out1();//B(out1)bb->out2();//B(out2)

打印

A(out1)
A(out2)
B(out1)
B(out2)
A(out1)
B(out2)
B(out1)
B(out2)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git简明教程

1.Git的定位 在我们自己开发项目的过程中&#xff0c;经常会遇到这样的情况&#xff0c;为了防止代码丢失&#xff0c;或者新变更的代码影响到原有的代码功能&#xff0c;为了在失误后能恢复到原来的版本&#xff0c;不得不复制出一个副本,比如&#xff1a;“坦克大战1.0”“坦…

【Html】交通灯问题

效果 实现方式 计时器&#xff1a;setTimeout或setInterval来计时。setInterval和 setTimeout 在某些情况下可能会出现计时不准确的情况。这通常是由于JavaScript的事件循环机制和其他代码执行所需的时间造成的。 问询&#xff1a;通过getCurrentLight将每个状态的持续时间设置…

Git常用的命令有哪些?

一、前言 git 的操作可以通过命令的形式如执行&#xff0c;日常使用就如下图6个命令即可 实际上&#xff0c;如果想要熟练使用&#xff0c;超过60多个命令需要了解&#xff0c;下面则介绍下常见的的git 命令 二、有哪些 配置 Git 自带一个 git config 的工具来帮助设置控制…

Haproxy 服务

Haproxy&#xff1a;他也是常用的负载均衡软件 nginx 支持四层转发&#xff0c;七层转发 haproxy 也是四层和七层转发 LVS的DR和NAT都是基于四层转发 都是基于流量的转发。 tun:四层和七层都有。 基于四层的转发&#xff1a; 1&#xff0c;lvs 2&#xff0c;nginx 3&…

对python中切片详解

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 Python中什么可以切片 Python中符合序列的有序序列都支持切片(slice) 如:列表,字符,元祖 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python源码、视…

“唯品会VIP商品API:一键获取奢侈品详情,尊享品质生活!“

要获取唯品会VIP商品的详细信息&#xff0c;您可以通过唯品会的API接口进行调用。 唯品会提供了多种商品选择&#xff0c;包括服装、美容护肤、鞋子、箱包、家居、母婴等等。在这些商品中&#xff0c;VIP奢侈品专区是唯品会的重要特色之一。 要获取VIP商品的详细信息&#xf…

计算机网络-计算机网络体系结构-应用层

目录 一、网络应用模型 客户/服务器模型(Client/Server) P2P模型(Peer-to-peer) 二、域名解析系统(DNS) 域名 域名服务器 解析过程 三、文件传输协议(FTP) FTP控制原理 四、电子邮件 组成结构 协议 SMTP MIME POP3 IMAP 五、万维网和HTTP协议 概述 HTTP 报…

程桌面管理软件Apple Remote Desktop mac中文介绍说明

Apple Remote Desktop mac是一款远程桌面管理软件。它可以让用户通过局域网或互联网连接到其他远程计算机&#xff0c;并实时监控和管理这些计算机。 使用Apple Remote Desktop&#xff0c;用户可以轻松远程操作和控制其他计算机的桌面。用户可以在远程计算机上查看、操控和键入…

WPF Material Design UI框架

前言 Material Design in xaml 是开源免费的ui框架&#xff0c;工控软件主打的就是简单界面。 以下简称MD 相关资源 MaterialDesignInXamlToolkit Github 地址 MD 快速启动 MD 案例压缩包 MD 框架使用 启动环境配置 安装Nuget包 App.xaml 配置 <Application x:Class&qu…

我用GPT4 预测了10年后中国大学排名Top10

目录 前言 &#x1f3eb;预测大学排名 &#x1f525; GPT4 预测排名的理由 最后 &#x1f388;个人主页&#xff1a;库库的里昂 &#x1f390;C/C领域新星创作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;杂谈&#x1f91d;希望作者的文章能…

Java IDEA feign调用上传文件MultipartFile以及实体对象亲测可行

Java IDEA feign调用上传文件MultipartFile以及实体对象亲测可行 1. 报错 java.lang.IllegalStateException: Body parameter cannot be used with form parameters2. 解决参考 1. 报错 java.lang.IllegalStateException: Body parameter cannot be used with form parameters …

海思专业级图像分析处理边缘计算盒子,双核A55 64位处理器+2.5TOPS算力

随着大模型、物联网等技术的快速发展&#xff0c;边缘计算逐渐成为了当今信息技术领域的研究热点&#xff0c;边缘算力的重要性愈加凸显。据市场需求反馈&#xff0c;大量长尾场景普遍具有小型化、灵活多变、即时性等特点&#xff0c;更需要关注算力的利用率、以及方案的成本、…

macOS telnet替代方式

前言 经过使用Linux&#xff0c;常常用Linux的telnet查看端口畅通&#xff0c;是否有防火墙&#xff0c;但是在mac上已经没有这个命令了&#xff0c;那么怎么使用这个命令或者有没有其他替代呢&#xff0c;win和linux是否可以使用相同的替代。macOS可以原生用nc命令替代&#…

公司电脑屏幕录制软件有什么功能

电脑屏幕录制软件有很多&#xff0c;今天简单说说说它的基础功能和附属功能&#xff1a; 基础功能&#xff1a; 1、屏幕录像 支持对所选电脑的屏幕进行录制&#xff0c;并且支持调整截屏频度、画面质量、单个视频时长等。 2、实时屏幕 可以对对方电脑进行实时屏幕查看&…

深入探究Selenium定位技巧及最佳实践

在使用Selenium进行Web自动化测试时&#xff0c;准确地定位元素是非常重要的一步。Selenium提供了多种元素定位方法&#xff0c;本文将深入探究这八大元素定位方法&#xff0c;帮助读者更好地理解和应用Selenium的定位技巧。 1. ID定位 ID是元素在HTML中的唯一标识符&#xff…

转载--关闭onenote2013 /中点击超链接(指向本地文件夹)后出现的安全声明 / Microsoft onenote2021 安全声明关闭

作者&#xff1a;匿名用户 链接&#xff1a;https://www.zhihu.com/question/32472113/answer/133076766以下为内容&#xff1a; 早上看到 第22条军规 的答案&#xff0c;果然很牛逼&#xff0c;感谢您。然后发现他的答案下面有人说不会用&#xff0c;所以我就写了一个包含图…

uniapp解决iOS切换语言——原生导航栏buttons文字不生效

uniapp 切换语言原生导航栏buttons文字不生效&#xff1f; 文章目录 uniapp 切换语言原生导航栏buttons文字不生效&#xff1f;效果图page.json配置解决方式 效果图 场景&#xff1a;在 tabbar 页面中&#xff0c;配置 原生导航栏 buttons &#xff0c;切换语言时&#xff0c;不…

微信小程序 picker-view 组件构建一个上下拖动选择器

picker-view是官方的一个选择器组件 支持多级选择 当然也可以单项选择 我们先来看看是个什么东西吧 简单写一个 wxml代码 <view><picker-view bindchange"pickerChange" style"width: 300rpx; height: 200rpx; font-size: 20px;"><!-- pic…

qt的一些自绘控件

https://download.csdn.net/download/venice0708/88469835

flutter开发实战-hero实现图片预览功能extend_image

flutter开发实战-hero实现图片预览功能extend_image 在开发中&#xff0c;经常遇到需要图片预览&#xff0c;当feed中点击一个图片&#xff0c;开启预览&#xff0c;多个图片可以左右切换swiper&#xff0c;双击图片及手势进行缩放功能。 这个主要实现使用extend_image插件。在…