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.mzph.cn/news/117171.shtml

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

相关文章

html web前端,登录,post请求提交 json带参

html web前端&#xff0c;登录&#xff0c;post请求提交 json带参 3ca9855b3fd279fa17d46f01dc652030.jpg <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title></t…

FPGA【紫光语法】

寄存器数据类型&#xff1a; reg 默认为 1 bit wide&#xff0c;如果超过 1 bit&#xff0c;则需要 range declaration 设置 reg 的位宽integer 默认位宽为 32 bit&#xff0c;不允许有 range declarationtime 默认位宽为 64 bit&#xff0c;不允许有 range declarat…

02-2、PyCharm中文乱码的三处解决方法

PyCharm中文乱码 修改处1&#xff1a; 修改处2&#xff1a;这个也没用 在Pycharm中可以创建一个模版&#xff0c;每次新建python文件时Pycharm会默认在前两行生成utf-8 #!/user/bin/env python3 # -- coding: utf-8 -- 还是乱码 再在这里设置以下 添加 &#xff1a; -Dfi…

IEEE754 标准存储浮点数

1. IEEE754 标准简介 IEEE754 标准是一种用于浮点数表示和运算的标准&#xff0c;由国际电工委员会&#xff08;IEEE&#xff09;制定。它定义了浮点数的编码格式、舍入规则以及基本的算术运算规则&#xff0c;旨在提供一种可移植性和一致性的方式来表示和处理浮点数 IEEE754 …

基于DF模式的协作通信技术matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、DF概述 4.2、DF基本原理 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2013b 3.部分核心程序 clc; clear; close all; warning off; addpath(genpath(pwd))…

Postman —— postman的介绍和安装

Postman的介绍 Postman 是一款谷歌开发的接口测试工具,使API的调试与测试更加便捷。 它提供功能强大的 Web API & HTTP 请求调试。它能够发送任何类型的HTTP 请求 (GET, HEAD, POST, PUT..)&#xff0c;附带任何数量的参数 headers postman是一款支持http协议的接口调试与…

OpenHarmony 持久化存储 UI 状态:PersistentStorage

前两个小节介绍的 LocalStorage 和 AppStorage 都是运行时的内存&#xff0c;但是在应用退出再次启动后&#xff0c;依然能保存选定的结果&#xff0c;是应用开发中十分常见的现象&#xff0c;这就需要用到 PersistentStorage。 PersistentStorage 是应用程序中的可选单例对象…

DAOS学习笔记及思考

DAOS带来的思考 根据daos docs的描述&#xff0c;DAOS是Intel基于NVMe全新设计开发并开源的异步对象存储&#xff0c;充分利用下一代NVMe技术的优势&#xff0c;对外提供KV存储接口&#xff0c;提供非阻塞事物I/O&#xff0c;端到端完整性&#xff0c;细粒度的数据控制&#x…

某马机房预约系统 C++项目(二) 完结

8.4、查看机房 8.4.1、添加机房信息 根据案例&#xff0c;我们还是先在computerRoom.txt中直接添加点数据 //几机房 机器数量 1 20 2 50 3 1008.4.2、机房类创建 ​ 同样我们在头文件下新建一个computerRoom.h文件 添加如下代码&#xff1a; #pragma once #include<i…

【python】文件和异常

文件和异常 实际开发中常常会遇到对数据进行持久化操作的场景&#xff0c;而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词&#xff0c;可能需要先科普一下关于文件系统的知识&#xff0c;但是这里我们并不浪费笔墨介绍这个概念&#xff0c;请大…

凉鞋的 Godot 笔记 204. 语句

204. 语句 在上一篇&#xff0c;我们接触了三种常见的类型&#xff0c;如下所示&#xff1a; 这样我们算是对变量进行了一个入门了。 其实我们除了变量&#xff0c;我们还接触了一个叫做语句的概念。 我们可以看下代码: extends Node# Called when the node enters the sce…

IDEA配置HTML和Thymeleaf热部署开发

IDEA配置HTML和Thymeleaf热部署开发 1.项目配置2. IDEA配置3. 使用 需求&#xff1a;现在我们在开发不分离项目的时候&#xff08;SpringBootThmeleaf&#xff09;经常会改动了类或者静态html文件就需要重启一下服务器&#xff0c; 这样不仅时间开销很大&#xff0c;而且经常重…

SpringMVC系列-5 消息转换器

背景 SpringMVC系列的第五篇介绍消息转换器&#xff0c;本文讨论的消息转换指代调用Controller接口后&#xff0c;对结果进行转换处理的过程。 内容包括介绍自定义消息转换器、SpringMVC常见的消息转换器、Spring消息转换器工作原理等三部分。 本文以 SpringMVC系列-2 HTTP请求…

PHP 预定义超全局变量 笔记/练习

预定义超全局数组变量 $_FILES 练习在最后 其他练习跟在每条笔记后 概述 预定义&#xff1a;预定义变量是 PHP 已定义&#xff0c;可以直接使用超全局&#xff1a;作用域是全局&#xff0c;可以在脚本的任何地方&#xff08;包括函数内部、外部&#xff09;都可以进行访问 常…

使用GoogleNet网络实现花朵分类

一.数据集准备 新建一个项目文件夹GoogleNet&#xff0c;并在里面建立data_set文件夹用来保存数据集&#xff0c;在data_set文件夹下创建新文件夹"flower_data"&#xff0c;点击链接下载花分类数据集https://storage.googleapis.com/download.tensorflow.org/exampl…

冲刺学习-MySQL-常见问题

MySQL索引的最左原则 联合索引的说明 建立三个字段的联合索引联合索引&#xff08;a&#xff0c;b&#xff0c;c&#xff09;相当于建立了索引&#xff1a;&#xff08;a&#xff09;&#xff0c;&#xff08;a&#xff0c;b&#xff09;&#xff0c;&#xff08;a&#xff0…

计算机考研自命题(5)

1、C语言–求和 1、展开式求和。输入一个实数x&#xff0c;计算并输出下式的和&#xff0c;直到最后一项的绝对值小于0.00001.计算结果保留2位小数&#xff0c;试编程。 S x x/2&#xff01; x/3&#xff01; … /* 算法思想&#xff1a;定义一个求阶乘的函数fact(), 头文件调…

蜣螂优化(DBO)求解置换流水车间调度问题(PFSP)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

基于SpringBoot的时间管理系统

基于SpringBoot的时间管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 用户界面 摘要 基于Spring Boot的时间管理系统是一款功能丰富…

Unity之ShaderGraph如何实现靠近显示溶解效果

前言 今天我们来实现一个我再B站看到的一个使用LeapMotion实现的用手部触摸就可以显示的溶解效果。 效果如下图所示: 主要节点 Position:提供对网格顶点或片段的Position 的访问,具体取决于节点所属图形部分的有效着色器阶段。使用Space下拉参数选择输出值的坐标空间。 …