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

news2025/1/25 4:23:38

请添加图片描述

文章目录

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


📝前言

上回我们学习了类的定义,初步了解了什么是类?类的定义,以及类的三个访问限定符: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 的大小。


🚩总结

请添加图片描述

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

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

相关文章

韩顺平Java | C24 MySQL数据库(下)

※多表查询 笛卡尔集&#xff1a;查询两个表&#xff0c;默认无条件情况下&#xff0c;取出第一张表中的每一条记录和第二张表的每一条记录进行组合&#xff0c;返回row1*row2条记录数&#xff0c;包含两张表的所有列 内连接 # 写出正确的过滤条件&#xff1a;多表查询条件不…

【linux】yum 和 vim

yum 和 vim 1. Linux 软件包管理器 yum1.1 什么是软件包1.2 查看软件包1.3 如何安装软件1.4 如何卸载软件1.5 关于 rzsz 2. Linux编辑器-vim使用2.1 vim的基本概念2.2 vim的基本操作2.3 vim命令模式命令集2.4 vim底行模式命令集2.5 vim操作总结补充&#xff1a;vim下批量化注释…

9. 软件登陆界面-2

窗口组件 1.组件的属性 组件的位置 组件的可视 2.组件的事件 窗口_创建完毕 窗口_托盘事件&#xff1b;带有参数的事件的使用方法。 3.组件的方法 置托盘图标 销毁&#xff08;&#xff09; 编辑框组件 1.编辑框的属性 内容 是否允许多行 输入方式 密码遮盖字符…

单链表专题

文章目录 目录1. 链表的概念及结构2. 实现单链表2.1 链表的打印2.2 链表的尾插2.3 链表的头插2.4 链表的尾删2.5 链表的头删2.6 查找2.7 在指定位置之前插入数据2.8 在指定位置之后插入数据2.9 删除pos节点2.10 删除pos之后的节点2.11 销毁链表 3. 链表的分类 目录 链表的概念…

【Linux】初识Linux操作系统

目录 一、shell 二、Linux命令的分类 三、Linux命令的格式 四、编辑Linux命令行的辅助操作 五、查看命令使用说明的方法 六、基础命令 一、shell ●Linux系统中运行的一个特殊程序&#xff0c;位于用户与内核之间 ●作用&#xff1a;作为“翻译官”&#xff0c;接收用户…

基于Java+SpringBoot+Vue网络相册设计与实现(源码+文档+部署+讲解)

一.系统概述 网络相册设计与实现的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起…

C++ 类和对象(中篇)

类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。空类中什么都没有吗&#xff1f;并不是的&#xff0c;任何一个类在我们不写的情 况下&#xff0c;都会自动生成下面6个默认成员函数。 构造函数&#xff1a; 定义&#xff1a;构造函数是一个特殊的成员…

【PHP系统学习】——Laravel框架数据库的连接以及数据库的增删改查的详细教程

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

【招贤纳士】长期有效

【招贤纳士】长期有效&#xff0c;有意者联系 一、SLAM算法工程师工作内容&#xff1a;任职资格&#xff1a; 二、规划算法工程师工作内容&#xff1a;任职资格&#xff1a; 工作地点&#xff1a;深圳南山 公司行业&#xff1a;家用扫地机器人 待遇从优&#xff0c;有机器人比赛…

CAD导入GIS平台常见问题大全

1.CAD导入图新地球报【坐标超出范围】、【导入失败】 一般是投影不对&#xff0c;多数是中央经线选错了&#xff0c;或者是没注意是否有带号 这种情况&#xff0c;先打开CAD软件&#xff0c;通过id命令看一下数据的坐标&#xff0c;如下图 看到坐标是这样式的&#xff0c;X达…

达梦数据库审计相关参数

达梦数据库审计相关参数 基础环境 操作系统&#xff1a;Red Hat Enterprise Linux Server release 7.9 (Maipo) 数据库版本&#xff1a;DM Database Server 64 V8 架构&#xff1a;单实例1 查看审计相关的参数 查看AUD相关的参数。 1.1 查看dm.ini配置文件。 在dm.ini配置文…

gurobi不同版本切换

每年年底&#xff0c;gurobi都会推出新版本。新版本是大的迭代更新&#xff0c;求解问题的效率和精度都会提升。官方人员一般会建议我们安装最新的版本&#xff0c;此外&#xff0c;写论文审稿专家也会建议我们使用较新的版本。 从我们现装的版本切换到新版本。我以往的做法是…

【CVE-2023-38831】进行钓鱼攻击的研究

本文仅仅是对相关漏洞利用的学习记录&#xff0c;请各位合法合规食用&#xff01; WinRAR是一款文件压缩器,该产品支持RAR、ZIP等格式文件的压缩和解压等。WinRAR在处理压缩包内同名的文件与文件夹时代码执行漏洞,攻击者构建由恶意文件与非恶意文件构成的特制压缩包文件,诱导受…

【负载均衡——一致性哈希算法】

1.一致性哈希是什么 一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时&#xff0c;发生过多的数据迁移的问题。 一致哈希算法也用了取模运算&#xff0c;但与哈希算法不同的是&#xff0c;哈希算法是对节点的数量进行取模运算&#xff0c;而一致哈希算法是对 2^32 进…

吴恩达机器学习理论基础—决策树模型

吴恩达机器学习理论基础—决策树模型 决策树模型&#xff08;Decision Trees&#xff09; 采用猫狗分类的数据集&#xff0c;同时拥有三个基本的特征&#xff08;输入&#xff09;作为模型建立时使用的数据集。 将构造出来的决策树&#xff0c;分为了决策结点和叶子节点&#…

【C++入门】内联函数、auto与基于范围的for循环

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

2024-04-08

作业要求&#xff1a; 1> 思维导图 2>使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否…

【日常记录】【JS】一道解构面试题

文章目录 1、描述2、分析与实现3、参考链接 1、描述 让这一段代码可以执行&#xff0c;并且正确输出 let [name, age] {name: 呆呆狗,age: 20}console.log(name, age);2、分析与实现 在浏览器上执行这段代码会报错 翻译以下&#xff1a;不是可迭代对象 可迭代对象&#xff08;…

Go——面向对象

一. 匿名字段 go支持只提供类型而不写字段名的方式&#xff0c;也就是匿名字段&#xff0c;也称为嵌入字段。 同名字段的情况 所以自定义类型和内置类型都可以作为匿名字段使用 指针类型匿名字段 二.接口 接口定义了一个对象的行为规范&#xff0c;但是定义规范不实现&#xff…

MT3022 召唤神龙

思路&#xff1a;二分答案 。check():检查组p套卡是否成立&#xff0c;即检查r卡是否足够组成p套卡。 &#xff08;易错点&#xff1a;check的思路&#xff0c;开long long&#xff09; #include <bits/stdc.h> using namespace std; long long int n, m; long long int…