C++奇迹之旅:探索类对象模型内存的存储猜想

请添加图片描述

文章目录

  • 📝前言
  • 🌠 类的实例化
    • 🌉类对象模型
  • 🌠 如何计算类对象的大小
    • 🌉类对象的存储方式猜想
      • 🌠猜想一:对象中包含类的各个成员
      • 🌉猜想二:代码只保存一份,在对象中保存存放代码的地址
      • 🌠猜想三:只保存成员变量,成员函数存放在公共的代码段
  • 🌉 类中什么都没有---空类
  • 🌉 类中仅有成员函数
  • 🌉 类中既有成员变量,又有成员函数
  • 🚩总结


📝前言

上回我们学习了类的定义,初步了解了什么是类?类的定义,以及类的三个访问限定符:publicprivateprotected,本小节将讲解类的实例化,类对象模型的猜想存储,及三种简单类的计算。

🌠 类的实例化

在 C++ 中,类的实例化是指创建一个类的对象。当我们定义了一个类之后,就可以根据这个类创建出多个对象。这个过程就称为类的实例化。

  • 实例化一个类的语法是:
ClassName objectName;

首先,假设我们定义了一个 Person 类:

  1. 类的声明:
    • 我们定义了一个 Person 类,包含了两个数据成员 nameage,以及一个成员函数 introduce()
    • 在类的声明阶段,并没有为 Person 类分配任何内存空间。
// 类的声明
#include <iostream>
using namespace std;
class Person 
{
public:
    std::string name;
    int age;
    void introduce() ;
};

  1. 类的定义:

    • 我们还需要定义 introduce() 函数的具体实现:
    void Person::introduce() 
    {
        std::cout << "My name is " << name << " and I'm " << age << " years old." << std::endl;
    }
    
    • 在类的定义阶段,同样没有为 Person 类分配任何内存空间。
  2. 类的实例化:

    • 现在我们可以创建 Person 类的对象了:
    int main() 
    {
          Person p1;
         p1.name = "Asen";
         p1.age = 18;
         p1.introduce(); 
    
    
        return 0;
    }
    

当我们创建 Person 对象 p1 时,系统会为 p1 分配内存空间,用于存储它的数据成员 nameage。此时,我们可以访问和修改 p1 对象的成员变量和成员函数。
在这里插入图片描述

通过这个例子,我们可以看到,类的声明和定义只是描述了类的结构,而类的实例化person p1这一步才是真正创建了类的对象并分配了内存空间。每个实例化的对象都有自己独立的内存空间,可以访问和修改自己的数据成员。

类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。
谜语:“年纪不大,胡子一把,主人来了,就喊妈妈” 谜底:山羊

当然,一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

int main()
{
 Person._age = 100;   // 编译失败:error C2059: 语法错误:“.”
 return 0;
}

注意:Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。

类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
在这里插入图片描述
刚才谈到:类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。那我不太信,那我算算看看:

#include <iostream>
using namespace std;
// 类的声明
class Person 
{
public:
    std::string name;
    int age;
    void introduce();
};
//类的函数定义
void Person::introduce()
{
    std::cout << "My name is " << name << " and I'm " << age << " years old." << std::endl;
}

int main() 
{
    cout << sizeof(Person) << endl;

    return 0;
}

在这里插入图片描述
利用sizeof(person)我们可以看到,即使你没有直接实例化一个 Person 对象,但是 sizeof(Person) 仍然算出一个32字节的值,这是为啥?

这是因为:

  1. 类的内存布局:
    当你定义一个类时,编译器会为这个类分配一定的内存空间,用于存储类的数据成员。
    即使你没有创建任何对象,编译器也需要知道这个类的内存布局,以便在需要创建对象时正确地分配内存。
  2. 编译时内存分配:
    在编译时,编译器会计算出类的总大小,包括所有数据成员的大小。这个总大小就是 sizeof(Person) 的结果。

🌉类对象模型

🌠 如何计算类对象的大小

不同以往的C语言结构体,问题是C++类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?
让我们来看一个生动容易简单的例子:

class calculate_one
{
public:
	void Init(char c1, char c2, int i)
	{
		_c1 = c1;
		_c2 = c2;
		_i = i;
	}

	void Print()
	{
		cout << _c1 << "-" << _c2 << "-" << _i << endl;
	}

	char _c1;
	char _c2;
	int _i;
};

int main()
{
	cout << sizeof(calculate_one) << endl;
}

这个类的大小是多少个字节呢?
首先我们想想结构体内存对齐规则:

  1. 结构体的第一个成员对齐到和结构体起始位置偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值
  • VS 中默认的值为 8
  • linuxgcc没有默认对齐数,对齐数就是成员自身的大小
  1. 结构体总大小为最大对齐数(结构体中的每一个成员都有一个对齐数,所有对齐数中的)的整数>倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

当我们去掉两个函数,计算出字节大小是8,这里不太理解结构体的计算,可以点击这里查找:
【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
在这里插入图片描述
当不去掉函数,发现,结果依然不改变。这里就涉及到了类各个成员的存储结构关系:

class calculate
{
public:
	void Init(char c1, char c2, int i)
	{
		_c1 = c1;
		_c2 = c2;
		_i = i;
	}

	void Print()
	{
		cout << _c1 << "-" << _c2 << "-" << _i << endl;
	}

	char _c1;
	char _c2;
	int _i;
};


int main()
{
	char a = 'A';
	char b = 'B';
	calculate cule1;
	cule1._i;
	calculate cule2;
	cule2._i;
	
	cule1.Init(a, b, 1)
	cule2.Init(a, b, 2);

	cout << sizeof(calculate) << endl;
}

当我们实例化两个对象时,编译器此时就会分配所需空间给两个对象

calculate cule1;
cule1._i;
calculate cule2;
cule2._i;

当我们再去,调用这两个对象里的函数时,会怎样?它怎么存储,看看汇编:

cule1.Init(a, b, 1)
cule2.Init(a, b, 2);

我们在我的C++奇迹之旅相遇:支持函数重载的原理也是提到call(函数的地址),call里的括号里的地址就是函数的地址
在这里插入图片描述
可以看出函数的地址是一样的,难道他们都在同一个地方存储函数,或者说在一个固定的公共区存储函数定义,需要时通过地址来查找,因此类对象的存储方式到底是什么样的?

🌉类对象的存储方式猜想

🌠猜想一:对象中包含类的各个成员

在这里插入图片描述
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?

🌉猜想二:代码只保存一份,在对象中保存存放代码的地址

在这里插入图片描述
节省内存空间,因为成员函数代码只保存一份,不需要为每个对象都保存一份函数代码,提高执行效率。因为只需要加载一份函数代码,不需要重复加载。支持多态,因为不同的对象可以有不同的函数指针,指向不同的函数实现,从而实现多态。

🌠猜想三:只保存成员变量,成员函数存放在公共的代码段

在这里插入图片描述
类的成员函数代码只保存一份,存放在程序的公共代码段中。每个类对象中只保存成员变量的实际数据。对象中不保存任何指向成员函数的指针。
当通过对象调用成员函数时,编译器会根据成员函数的名称和类型,找到对应的函数代码地址,并传入对象自身的this指针,来完成函数的调用

总结:由于每个成员或者对象的函数地址都是一样的,因此猜想三更合理:只保存成员变量,成员函数存放在公共的代码段

我们再通过对下面的不同对象分别获取大小来分析看下

🌉 类中什么都没有—空类

class A3
{};

在这里插入图片描述
声明一个空的类 A3,编译器仍然需要为该类的实例分配内存空间。即使这个类没有任何成员变量或成员函数,每个对象也需要在内存中占据至少一个字节的空间。这是因为在C++中,每个对象都必须具有唯一的内存地址,以便程序能够准确地引用它们

这个额外的字节通常被称为“空对象占位符”或“填充字节”,它确保每个对象都有独特的地址。这个字节不会存储任何数据,但是确保了对象在内存中的唯一性,使得程序能够正确地对其进行操作

这种行为在C++标准中没有明确规定,而是由具体的编译器实现来决定。通常情况下,编译器会为了内存对齐的需要而分配这个额外的字节,以确保对象在内存中的布局符合特定的对齐要求

所以,即使类 A3 是空的,它的大小也会被编译器分配为至少1字节,以确保每个对象都具有唯一的内存地址

🌉 类中仅有成员函数

class A2 
{
public:
	void f2() {}
};

在这里插入图片描述
即使类中仅有成员函数而没有任何成员变量,C++编译器仍然会为该类的实例分配至少个字节的内存空间。这是因为每个对象都必须具有唯一的内存地址,以便程序能够准确地引用它们。

🌉 类中既有成员变量,又有成员函数

class A1 
{
public:
	void f1() {}
private:
	int _a;
};

在这里插入图片描述
在这个示例中,类 A1 中有一个私有成员变量 _a 和一个公有成员函数 f1()。根据C++的规则,成员函数不会影响类的大小,因为它们不会被存储在每个对象中。所以,f1() 不会影响 sizeof(A1) 的值。

然而,类 A1 中包含一个 int 类型的私有成员变量 _a。在32位系统上,int 类型通常占用4个字节的内存空间。因此sizeof(A1) 的大小是4个字节,这个大小正是 _a 的大小。


🚩总结

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

特征融合篇 | RTDETR引入基于内容引导的特征融合方法 | IEEE TIP 2024

本改进已集成到 RT-DETR-Magic 框架。 摘要—单幅图像去雾是一个具有挑战性的不适定问题,它从观察到的雾化图像中估计潜在的无雾图像。一些现有的基于深度学习的方法致力于通过增加卷积的深度或宽度来改善模型性能。卷积神经网络(CNN)结构的学习能力仍然未被充分探索。本文提…

Prometheus+grafana监控nacos和spring-boot服务(增加自定义指标)(七)

前面记录了项目中常用的各种中间件的指标采集器的用法及搭建方式 &#xff0c; 由于所有组件写一篇幅过长&#xff0c;所以每个组件分一篇方便查看&#xff0c;前六篇链接如下 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana…

【3GPP】【核心网】核心网/蜂窝网络重点知识面试题二(超详细)

1. 欢迎大家订阅和关注&#xff0c;3GPP通信协议精讲&#xff08;2G/3G/4G/5G/IMS&#xff09;知识点&#xff0c;专栏会持续更新中.....敬请期待&#xff01; 目录 1. 对于主要的LTE核心网接口&#xff0c;给出运行在该接口上数据的协议栈&#xff0c;并给出协议特征 2. 通常…

电压继电器SRMUVS-220VAC-2H2D 导轨安装 JOSEF约瑟

系列型号&#xff1a; SRMUVS-58VAC-2H欠电压监视继电器&#xff1b;SRMUVS-100VAC-2H欠电压监视继电器&#xff1b; SRMUVS-110VAC-2H欠电压监视继电器&#xff1b;SRMUVS-220VAC-2H欠电压监视继电器&#xff1b; SRMUVS-58VAC-2H2D欠电压监视继电器&#xff1b;SRMUVS-100…

python爬虫 爬取网页图片

http://t.csdnimg.cn/iQgHw //爬虫爬取图片其实是很简单的&#xff0c;但是大多数同学&#xff0c;可能对 url的设置一直有困惑&#xff08;这点本人也在研究&#xff09;&#xff0c;而本篇文章&#xff0c;对于想要爬取图片的小白简直是福利。你只需要将文章代码运行即可&am…

自动化测试面试题及答案大全

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【YOLOv9】完胜V8的SOTA模型Yolov9(论文阅读笔记)

官方论文地址: 论文地址点击即可跳转 官方代码地址: GitCode - 开发者的代码家园 官方代码地址点击即可跳转 1 总述 当输入数据经过各层的特征提取和变换的时候,都会丢失一定的信息。针对这一问题:

顺序表讲解

一.数据结构 在学习顺序表之前&#xff0c;我们先需要了解什么是数据结构。 1.什么是数据结构呢&#xff1f; 数据结构是由“数据”和结构两词组合而来。 什么是数据呢&#xff1f; 你的游戏账号&#xff0c;身份信息&#xff0c;网页里的信息&#xff08;文字&#xff0c…

onSaveInstanceState()与onRestoreInstanceState()

目录 1.二者作用 2.onSaveInstanceState调用时机 2.1 五种情况 前4种情况Activity生命周期&#xff1a; 2.2 注意事项&#xff1a;确定会被系统回收并销毁&#xff0c;不会调用此方法 两个例子 3.onRestoreInstanceState调用时机 3.1实例——屏幕切换生命周期 3.2 极端…

Python实现读取dxf文件的所有字符

Python实现读取dxf文件的所有字符 import ezdxfdef read_dxf_and_print_text(filename):# 加载DXF文件doc ezdxf.readfile(filename)# 遍历所有的实体for entity in doc.entities:# 检查实体是否是TEXT、MTEXT或DIMENSIONif isinstance(entity, ezdxf.entities.Text):print(f…

初识--数据结构

什么是数据结构&#xff1f;我们为什么要学习数据结构呢....一系列的问题就促使我们不得不了解数据结构。我们不禁要问了&#xff0c;学习C语言不就够了吗&#xff1f;为什么还要学习数据结构呢&#xff1f;这是因为&#xff1a;数据结构能够解决C语言解决不了的问题&#xff0…

Unity多线程简单示例

using UnityEngine; using System.Threading;public class texxxst : MonoBehaviour {Thread thread;void Start(){// 创建一个新的线程&#xff0c;并传入要执行的方法thread new Thread(new ThreadStart(DoWork));// 启动线程thread.Start();}void DoWork(){for (int i 0; …

数据降维方法-主成分分析(PCA)

目录 一、前言 二、向量的表示及基变换 三、基变换 四、协方差矩阵 五、协方差 六、优化目标 一、前言 主成分分析(Principal Component Analysis) 用途&#xff1a;降维中的常用手段 目标&#xff1a;提取最有价值的信息&#xff08;基于方差&#xff09; 问题&#x…

【项目精讲】RESTful简洁描述

RESTful是什么 是一种架构风格/API设计规范将一切数据视为资源利用HTTP请求方式 POST、GET、PUT、DELETE&#xff0c;描述对资源的操作 GET 获取资源POST 新建资源PUT 更新资源DELETE 删除资源 通过HTTP响应状态码&#xff0c;描述对资源的操作结果请求数据和英大数据均为JSO…

YOLOv8模型剪枝实战:DepGraph(依赖图)方法

课程链接&#xff1a;YOLOv8模型剪枝实战&#xff1a;DepGraph(依赖图)方法_在线视频教程-CSDN程序员研修院 YOLOv8是一个当前非常流行的目标检测器&#xff0c;本课程使用DepGraph&#xff08;依赖图&#xff09;剪枝方法对YOLOv8进行网络剪枝&#xff0c;使其更加轻量和实用…

SL4010 低压升压恒压芯片 2.7-24V输入 输出30V/10A 300W功率

SL4010是一款高效能、宽电压范围的低压升压恒压芯片&#xff0c;其卓越的性能和广泛的应用领域使其在市场上备受瞩目。该芯片支持2.7-24V的宽输入电压范围&#xff0c;能够提供稳定的30V/10A输出&#xff0c;最大输出功率高达300W&#xff0c;为各种电子设备提供稳定可靠的电源…

【动态规划-线性dp】【蓝桥杯备考训练】:乌龟棋、最长上升子序列、最长公共子序列、松散子序列、最大上升子序列和【已更新完成】

目录 1、乌龟棋 2、最长上升子序列 3、最长公共子序列 4、松散子序列 5、最大上升子序列和 1、乌龟棋 小明过生日的时候&#xff0c;爸爸送给他一副乌龟棋当作礼物。 乌龟棋的棋盘只有一行&#xff0c;该行有 N 个格子&#xff0c;每个格子上一个分数&#xff08;非负整…

【科研入门】搭建与配置云服务器的论文环境

如何搭建云服务器并且配置基础论文代码环境&#xff1f; 目录 如何搭建云服务器并且配置基础论文代码环境&#xff1f;前言一、租用服务器并选定基础配置具体建议 二、容器初始化三、项目的具体配置四、克隆五、常见问题 前言 此次环境配置以AutoDL为例(可以选用其他服务器租用…

PS入门|学PS一定要先知道图层这玩意儿

前言 开始学习PS的小伙伴肯定是会遇到很多问题&#xff0c;最常见的莫过于为啥我调整了某些参数之后&#xff0c;并没有任何作用。 这个就涉及到图层的问题了。 学PS一定要知道&#xff0c;图层面板怎么看。 正文开始 首先咱们讲的图层面板基本上是在PS里100%会用到的功能。…

jfinal项目操作数据库DB.find()空指针异常问题解决

排查ActiveRecordPlugin是否配置启动成功 查看项目的配置 里面这个别注释了 这样就能和数据库交互了