面向对象编程基础:类的实例化与对象内存模型详解
目录
一、类的实例化详解
1、什么是类的实例化?
2、关键概念
类如同模型或蓝图
实例化创建实际对象
现实类比
2、代码示例
示例说明
3、重要结论(重要!!!)
二、类对象模型与内存布局详解
1、类对象的内存结构(重要!!!)
成员变量存储
成员函数存储
存储效率考量
2、类对象存储模型图示
3、类大小计算规则
基本规则
特殊类大小说明
4、验证示例
类Person的对象内存布局:
5、结构体内存对齐规则详解
基本规则
VS编译器默认值
嵌套结构体规则
对齐示例
6、重要结论
一、类的实例化详解
1、什么是类的实例化?
-
类实例化是指用类类型在物理内存中创建对象的过程
-
类是对对象的一种抽象描述,是一种模型或蓝图
-
类限定了成员变量和成员函数,但这些只是声明,不会分配实际内存空间
2、关键概念
类如同模型或蓝图
-
类定义仅规定了类包含哪些成员(属性和方法)
-
定义类时并不会分配实际的内存空间
-
类似于C语言中定义结构体类型但不创建变量
实例化创建实际对象
-
一个类可以实例化出多个对象,每个对象都是独立的实体,互不干扰
-
只有在实例化对象时,系统才会为成员变量分配实际的物理内存空间
-
每个对象占用独立的物理内存空间存储其成员变量
-
类似于用结构体类型创建变量时分配实际内存
现实类比
类实例化对象就像使用建筑设计图建造房子:
-
类相当于设计图:规划了房间数量、大小和功能,图纸本身(类)没有实体存在
-
实例化就是根据图纸建造实际的房子
-
对象相当于实际建造的房子,只有建造出的房子(对象)才占用物理空间:可以实际使用和居住
-
设计图(类)本身不能住人,只有建好的房子(对象)才能住人
2、代码示例
#include<iostream>
using namespace std;class Date {
public:// 初始化日期void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}// 打印日期void Print() {cout << _year << "/" << _month << "/" << _day << endl;}private:// 这里只是声明成员变量,没有实际分配空间int _year;int _month;int _day;
};int main() {// Date类实例化出对象d1和d2Date d1; // 第一个Date对象Date d2; // 第二个Date对象// 初始化并打印d1d1.Init(2024, 3, 31);d1.Print();// 初始化并打印d2d2.Init(2024, 7, 5);d2.Print();return 0;
}
示例说明
-
类定义阶段:
-
Date
类定义了三个私有成员变量(_year
,_month
,_day
) -
此时只是声明,没有分配内存空间
-
-
实例化阶段:
-
Date d1
和Date d2
语句实际创建了两个Date对象 -
此时系统为每个对象分配独立的内存空间来存储其成员变量
-
-
对象使用:
-
每个对象可以独立调用成员函数(
Init()
和Print()
) -
对象之间的数据互不干扰(d1和d2存储不同的日期值)
-
3、重要结论(重要!!!)
-
类定义只是描述了对象的结构和行为
-
只有实例化后才会为对象分配内存,才能实际使用类的功能
-
每个对象都有自己独立的成员变量存储空间,每个对象都有自己独立的状态(成员变量值)
-
类的方法(成员函数)被所有对象共享,不占用对象的内存空间
-
类是抽象的模板,对象是具体的实例
理解类的实例化是面向对象编程的基础,它体现了"抽象"与"具体"的关系。
二、类对象模型与内存布局详解
1、类对象的内存结构(重要!!!)
类对象在内存中的存储方式遵循以下原则:
成员变量存储
-
每个对象独立存储自己的成员变量
-
不同对象的成员变量值互不影响
-
成员变量按照结构体内存对齐规则排列
成员函数存储
-
所有对象共享同一份成员函数代码
-
成员函数存储在公共代码区,不占用对象的内存空间
-
成员函数编译后成为指令代码,存储在代码段(公共代码区)
-
对象中不存储成员函数指针(避免重复存储浪费空间)
-
函数调用地址在编译链接时确定,运行时直接通过固定地址调用
存储效率考量
-
若存储函数指针,100个对象将重复存储100次相同指针
-
实际只需在代码段保存一份函数实现,所有对象共享
2、类对象存储模型图示
3、类大小计算规则
基本规则
-
类大小等于其所有非静态成员变量大小之和(考虑内存对齐)
-
静态成员变量不占用类对象空间(存储在全局区)
-
成员函数不占用类对象空间
特殊类大小说明
-
空类大小:
-
大小为1字节(编译器分配的占位标识,确保对象有唯一地址)
-
示例:
class C{};
→ 1字节
-
-
仅有成员函数的类:
-
大小同样为1字节
-
示例:
class B{void Print(){}};
→ 1字节
-
-
含成员变量的类:
-
大小为成员变量总和(考虑对齐)
-
示例:
class A{char _ch; int _i;};
→ 8字节(1+3填充+4)
-
4、验证示例
#include <iostream>
using namespace std;class Person
{
public:void ShowInfo(){cout << _name << "-" << _sex << "-" << _age << endl;}public:char *_name; // 姓名 (指针类型,通常4/8字节)char *_sex; // 性别 (指针类型,通常4/8字节)int _age; // 年龄 (通常4字节)
};// 测试类
class A1
{
public:void f1() {}private:int _a;
};
class A2
{
public:void f2() {}
};
class A3
{
};int main()
{cout << "Person size: " << sizeof(Person) << endl; // 通常12/24字节(取决于指针大小)cout << "A1 size: " << sizeof(A1) << endl; // 4 (int)cout << "A2 size: " << sizeof(A2) << endl; // 1 (只有成员函数)cout << "A3 size: " << sizeof(A3) << endl; // 1 (空类)return 0;
}
类Person的对象内存布局:
5、结构体内存对齐规则详解
基本规则
- 第一个成员变量位于偏移量0处
- 后续成员对齐到min(成员大小, 编译器默认对齐数)的整数倍地址
- 结构体总大小为最大对齐数的整数倍
VS编译器默认值
- 默认对齐数为8字节
- 实际对齐数取成员大小和默认对齐数的较小值
嵌套结构体规则
- 嵌套结构体对齐到其自身最大对齐数的整数倍处
- 整体大小是所有最大对齐数(含嵌套结构体)的整数倍
对齐示例
struct Example {char a; // 1字节,偏移0// 3字节填充(因为int要对齐到4)int b; // 4字节,偏移4short c; // 2字节,偏移8// 2字节填充(总大小需为4的整数倍)
}; // 总大小:12字节
6、重要结论
-
对象只存储成员变量,成员函数存储在公共代码区(代码段)
-
类大小计算只考虑成员变量,需遵循内存对齐规则,可能包含填充字节
-
空类有1字节占位符,保证每个对象有唯一地址,仅有成员函数的类大小也为1字节(占位标识)
-
内存对齐能提高CPU访问效率,但可能增加空间开销(内存对齐牺牲部分空间换取CPU访问效率提升)
-
实际开发中应注意类成员排列顺序以优化内存使用