C++从入门到实战(六)类和对象(第二部分)C++成员对象及其实例化,对象大小与this详解

C++从入门到实战(六)类和对象(第二部分)C++成员对象及其实例化,对象大小与this详解


前言

  • 在上一篇博客中,我们初步认识了 C++ 类的核心概念:类是数据与操作的封装体,通过访问限定符(public/private/protected)实现数据保护,并利用类域(::)明确成员归属

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

本篇我们将深入探索类的实例化过程、对象内存布局以及 this指针的奥秘,解决以下关键问题:

  • 类的成员变量和成员函数在内存中如何存储?
  • 为什么空类的对象大小是 1 字节?
  • 成员函数如何区分不同对象的数据?

一、类和对象里面成员变量,成员函数是什么

  • 首先我们来看一段代码
cpp 复制代码
#include<iostream>
using namespace std;

class Data
{
private:

	// 这⾥只是声明,没有开空间
	int _year;
	int _month;
	int _day;

public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
};

int main()
{
	// Date类实例化出对象d1和d2
	Data d1;
	Data d2;
	d1.Init(2025,3,21);
	d1.Print();
	d2.Init(2024, 3, 21);
	d2.Print();

}
  • 根据上次博客的内容,我们知道class Data 定义了一个名为 Data 的类,private:为私有的,public为共有的
  • 在C++里,什么是成员变量,什么是成员函数呢

1.1成员变量

  • 在 C++ 中,类是一种用户自定义的数据类型,它将数据和操作这些数据的函数封装在一起。
  • 类中的数据被称为成员变量,而操作这些数据的函数则被称为成员函数
  • 成员变量是类的属性,用于存储对象的状态信息。
  • 在类定义中声明成员变量,但只有在创建类的对象时,才会为这些成员变量分配内存空间。成员变量可以有不同的访问权限,如 private、protected 和 public
  • 例如上面代码里面的
cpp 复制代码
private:

	// 这⾥只是声明,没有开空间
	int _year;
	int _month;
	int _day;

1.2成员函数

  • 成员函数是类的行为,用于对成员变量进行操作。成员函数可以访问和修改类的成员变量,也可以调用其他成员函数。成员函数同样可以有不同的访问权限,其调用规则与成员变量的访问规则类似。
  • 例如上面代码里面的
cpp 复制代码
 // 成员函数,用于初始化对象的成员变量
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // 成员函数,用于打印对象的日期信息
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
  • 代码中成员变量和成员函数的详细信息
类型 名称 访问权限 作用
成员变量 _year private 存储日期的年份
成员变量 _month private 存储日期的月份
成员变量 _day private 存储日期的日
成员函数 Init(int year, int month, int day) public 初始化对象的 _year_month_day 成员变量
成员函数 Print() public 输出对象的日期信息

1.3成员变量、成员函数与局部变量的对比

对比项 成员变量 成员函数 局部变量
定义位置 类的定义内部 类的定义内部 函数或代码块内部
作用域 整个类的对象 整个类的对象 定义它的函数或代码块内部
生命周期 与对象的生命周期相同 与对象的生命周期相同 从定义处开始,到函数或代码块结束
内存分配 在对象创建时分配内存 存储在代码段,不随对象分配内存 在栈上分配内存
访问权限 可以有 privateprotectedpublic 等访问权限 可以有 privateprotectedpublic 等访问权限 无访问权限概念,只能在其作用域内访问
使用目的 存储对象的状态信息 实现对象的行为和操作 临时存储函数或代码块内的数据

二、类的实例化

  • 实例化是面向对象编程的核心操作,它将抽象的类转化为可操作的实体,支持封装、复用和多态等特性。

2.1什么是实例化,实例化的概念

实例化概念

  • 实例化是⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。
  • 类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间

咱们来通俗地讲讲实例化是啥。

  • 你可以把类想象成一张建筑设计图。
  • 这张设计图上详细规划好了房子有几个房间,每个房间的大小、功能等情况,但是它仅仅是一张图,还没有实实在在的房子建起来,也不能让人住进去。
  • 在编程里,类就是这样一种抽象的描述,它规定了有哪些成员变量,不过这些成员变量只是声明出来了,并没有给它们分配实际的存储空间。
  • 而实例化呢,就好比按照这张设计图去建造房子。用类类型 在物理内存里创建对象的这个过程,就是实例化。当我们实例化出对象时,就相当于房子建好了,这时候就会给成员变量分配实际的空间,对象就可以存储数据啦。
  • 总而言之
  • 类 ≈ 房子设计图
  • 实例化 ≈ 按图纸建造出真实房子
  • 对象 ≈ 建好的真实房子

2.2类的实例化过程

  • 我们以下面的这段代码为例,来讲一下类的实例化过程
cpp 复制代码
#include<iostream>
using namespace std;

class Data
{
private:

	// 这⾥只是声明,没有开空间
	int _year;
	int _month;
	int _day;

public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
};

int main()
{
	// Date类实例化出对象d1和d2
	Data d1;
	Data d2;
	d1.Init(2025,3,21);
	d1.Print();
	d2.Init(2024, 3, 21);
	d2.Print();

}

1. 类的定义

  • 首先我们需要对类进行定义
cpp 复制代码
class Data
{
private:
    // 这里只是声明,没有开空间
    int _year;
    int _month;
    int _day;

public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

2. 实例化对象

  • 接着在main函数里面进行实例化对象
  • 在 main 函数中,使用 Data 类实例化出了两个对象 d1 和 d2。实例化的过程实际上就是根据 Data 类这个模板
  • 在内存中为对象 d1 和 d2 分配空间,并且每个对象都有自己独立的成员变量副本。
cpp 复制代码
Data d1;
Data d2;

3.初始化对象

  • 实例化对象之后,需要对对象的成员变量进行初始化。
  • 在代码中,通过调用 Init 成员函数,为 d1 和 d2 的成员变量赋予具体的值。
cpp 复制代码
d1.Init(2025, 3, 21);
d2.Init(2024, 3, 21);

4. 访问对象的成员函数

  • 对象初始化完成后,就可以调用对象的成员函数来执行特定的操作。在代码中,调用了 Print 成员函数,用于输出对象的日期信息。

三、对象大小

C++ 里类实例化的对象要遵循内存对齐规则,目的是提升内存访问效率,具体规则如下:

  • 首个成员:首个成员存于结构体偏移量为 0 的地址处。
  • 其他成员:其他成员变量要对齐到某个对齐数的整数倍地址处。对齐数是编译器默认对齐数与该成员大小的较小值,像 VS 中默认对齐数是 8
  • 结构体总大小:结构体总大小需是最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
  • 嵌套结构体:若嵌套了结构体,嵌套的结构体要对齐到自身最大对齐数的整数倍处,结构体整体大小是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

类对象大小计算示例

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};
class B
{
public:
	void Print()
	{
		//...
	}
};

class C
{
};

int main()
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl; 
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
}
  • 可以发现:
    • A类有成员变量char _ch和int _i,其对象大小按内存对齐规则计算
    • B类和C类没有成员变量,但它们的对象大小是 1 字节,这是为了占位,以此标识对象存在

四、this指针

cpp 复制代码
#include<iostream>
using namespace std;

class Data
{
private:

	// 这⾥只是声明,没有开空间
	int _year;
	int _month;
	int _day;

public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
};

int main()
{
	// Date类实例化出对象d1和d2
	Data d1;
	Data d2;
	d1.Init(2025,3,21);
	d1.Print();
	d2.Init(2024, 3, 21);
	d2.Print();

}
  • 在Date类里有Init和Print这两个成员函数,在函数体中并没有对不同对象加以区分。
  • 但当对象d1调用Init和Print函数时,函数是怎么知道要访问的是d1对象的数据,而不是d2对象的数据呢?C++ 引入了隐含的this指针来解决这个问题

4.1 this的原理

  • 编译器在编译类的成员函数时,会默认在形参的第一个位置添加一个当前类类型的指针,也就是this指针。
  • 例如,Date类的Init函数,其真实的原型其实是
cpp 复制代码
void Init(Date* const this, int year, int month, int day) 

4.2 this指针的作用

  • 在类的成员函数里访问成员变量,本质上都是通过this指针来访问的。
  • 比如在Init函数里给_year赋值,实际上是this->_year = year,只是通常可以省略this->,直接写成_year = year

4.3 this指针的使用规则

实参和形参位置

  • C++ 规定不能在实参和形参的位置显式地写this指针,编译器在编译时会自动处理。例如在main函数里调用d1.Init(2024, 3, 31),这里不需要写成d1.Init(&d1, 2024, 3, 31) 。
    函数体内
  • 可以在函数体内显式地使用this指针。比如在Init函数里,可以写成this->_month = month; 。不过需要注意的是,this指针是一个常量指针,不能对其进行赋值操作,像this = nullptr; 这样的代码会编译报错。

总结核心概念速记

类对象的内存本质 = 成员变量集合 + 内存对齐 + this指针隐式传递

一、成员变量与成员函数

对比项 成员变量 成员函数 局部变量
定义位置 类体内 类体内 函数/代码块内
内存分配 随对象实例化分配 存储于代码段(共享) 栈上分配
作用域 类作用域 类作用域 局部作用域
生命周期 与对象共存亡 程序运行期间始终存在 随函数调用结束销毁

二、类的实例化与对象大小

  1. 实例化本质

    • 类 → 设计图,对象 → 实体房屋
    • 每个对象独立拥有成员变量副本,成员函数共享代码段
  2. 内存对齐规则(VS 编译器):

    • 第⼀个成员起始地址为 0
    • 其他成员对齐到 min(成员大小, 默认对齐数8) 的整数倍
    • 总大小为 最大对齐数的整数倍
    • 空类大小为 1 字节(占位符,标识对象存在)

三、this指针的作用

  • 核心作用区分成员函数操作的对象

  • 隐式传递 :编译器自动为成员函数添加 this 形参

    cpp 复制代码
    // 代码写法(隐式)
    void Init(int year) { _year = year; }
    
    // 实际原型(显式)
    void Init(Date* const this, int year) { this->_year = year; }
  • 使用规则

    • 不能显式修改 this 指针(this = nullptr 编译错误)
    • 可显式使用 this->成员 访问变量
知识图谱
复制代码
类和对象(下)  
├─ 成员构成  
│  ├─ 成员变量(数据)  
│  └─ 成员函数(操作)→ 共享代码段  
├─ 实例化  
│  ├─ 对象 = 成员变量集合 + 对齐填充  
│  └─ 空类大小 = 1 字节(占位)  
├─ 内存对齐  
│  ├─ 规则:偏移量、对齐数、总大小  
│  └─ 目的:提升 CPU 访问效率  
└─ this 指针  
   ├─ 隐式参数:指向调用对象  
   └─ 作用:区分多对象调用时的数据归属  
常见问题与误区

Q1:成员函数可以不定义吗?

  • A :可以声明为 = delete(禁止调用),或完全不定义(链接错误)。

Q2:为什么空类对象大小不是 0?

  • A:C++ 要求每个对象必须有唯一地址,1 字节用于占位。

Q3:this 指针是存储在对象里吗?

  • A :不是!this 是编译器隐含的形参,通过寄存器传递(如 ecx),不占用对象内存。
技术对比表

面向过程 vs 面向对象(类的优势)

维度 面向过程(C) 面向对象(C++类)
数据管理 全局变量,易冲突 封装为类成员,数据安全
代码复用 函数复制粘贴 继承/多态,高内聚低耦合
可维护性 牵一发而动全身 类接口隔离,修改局部化

以上就是这篇博客的全部内容,下一篇我们将继续探索更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

|--------------------|
| 非常感谢您的阅读,喜欢的话记得三连哦 |

相关推荐
OpenLoong 开源社区4 分钟前
技术视界 | 灵巧手的工作空间:解锁机器人精细操作的无限可能
人工智能·深度学习·算法·开源·人形机器人
SummerGao.19 分钟前
【实操】Mybatis-plus2.x升级到3.x
java·spring boot·mybatisplus·系统升级
珹洺22 分钟前
C++从入门到实战(五)类和对象(第一部分)为什么有类,及怎么使用类,类域概念详解(附带图谱等更好对比理解)
java·c语言·开发语言·数据结构·c++·redis·缓存
就改了23 分钟前
SpringMVC 跨域问题两种常用解决方案
java·springmvc
凌乱的程序猿24 分钟前
安装和部署Tomcat并在idea创建web文件
java·前端·tomcat
Evand J32 分钟前
GNSS(GPS、北斗等)与UWB的融合定位例程,matlab,二维平面,使用卡尔曼滤波
开发语言·matlab·平面
辰尘_星启32 分钟前
【Gen6D】位姿估计部署日志
人工智能·pytorch·深度学习·算法·位姿估计·感知
居然有人65439 分钟前
45.图论3
算法·深度优先·图论
笑口常开xpr40 分钟前
C 语 言 --- 扫 雷 游 戏(初 阶 版)
c语言·开发语言
Cindy辛蒂1 小时前
C语言:穷举法编程韩信点兵问题四种做法
c语言·开发语言·算法