类和对象(上):class 与 struct、实例化、内存对齐与 this 指针
我的github:(https://github.com/xcx55/ubuntu-linux-project)
感谢各位大佬参观我的github!!
C++ 入门阶段全部走完,从这篇开始进类和对象------C++ 真正的范式转变。先把"上"这块写掉:类怎么定义、实例化是怎么回事、对象里到底存了什么,以及无处不在却永远看不见的 this 指针。
一、class 与 struct:差的只是一个默认权限
C++ 里 struct 升级成了类:也可以定义函数 ,而且完全兼容 C 的用法。class 是专门的关键字。两者几乎一样,唯一区别是默认访问权限:
- class:成员默认 private(私有);
- struct:成员默认 public(公有)。
cpp
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year; // 如果成员叫 year,这里就写成 year = year,分不清谁是谁
_month = month;
_day = day;
}
private:
int _year; // 成员变量习惯加 _ 或 m 开头做标识
int _month;
int _day;
};
成员变量加 _ 不是强制的,只是惯例(也有公司用 m_ 开头),作用是让"给成员赋值"这种场景一眼分清哪边是成员哪边是参数。访问限定符有 public / private / protected 三种,一个类里可以放多个,谁生效看它圈住的范围。
访问限定符是"封装"的语法载体:C 语言里结构体的内部元素谁都能直接访问,C++ 里只能通过公开的成员函数去操作,看不到底层------这就是面向对象的第一个特性"封装"。类的设计本身就是体现封装。
二、域的账又续了一笔:类域
查找规则的老朋友回归了。C++ 的域扩到四种:局部域、全局域、命名空间域、类域。
编译器找名字还是那条路:先局部域,再全局域;而命名空间域和类域里的东西,得有单独的方法访问 (bit::x、obj.func())。类域还有一个新性质:就算两个函数长得一模一样,域不一样就是两个不同的函数------会生成两个看似相同实则不同的东西。这为后面的函数重载(依赖参数区分)和类成员函数(依赖 this 区分对象)铺了路。
还有一条通用规律值得单独记:对函数,先找声明,再找定义。声明可以在局部域、全局域、命名空间域、类域任何地方,找到声明编译器才知道去哪找定义。
三、实例化:声明没有空间,定义才有
这是全篇最核心的一句话:
声明没有空间,定义才有。类实例化------从声明变成定义------出来的东西就是对象。
类是对对象的一种抽象描述,像建筑设计图 :图纸规划了有几个房间、多大,但图纸本身不能住人;用图纸盖出房子,房子才能住。类本身不存储数据,Date d1; 这一下才在物理内存里分配空间------这个过程叫实例化,一个类可以实例化出任意多个对象。
四、对象里存什么:只有成员变量
对象里不存成员函数,只存成员变量。为什么?节约内存------同一个类的所有对象,成员函数的逻辑一模一样,没必要每个对象都复制一份函数代码。
那成员函数存在哪?代码段 。所有对象共用一份函数代码,靠 this 指针区分"这次调用操作的是哪个对象"(下一节)。所以计算一个类的大小,只算成员变量、按 C 语言结构体内存对齐的规则算。
为什么内存对齐
计算机一次能读到的字节数是固定的(比如 4 或 8)。拿 char _ch; int _i; 举例:
- 对齐存放:_ch 占 1 字节,_i 从偏移 4 开始,总共 8 字节,读 _i 只需一次对齐的访问;
- 不对齐 :_ch 后面紧跟 _i,_i 横跨两次读取边界,得读两次再拼才能拿到一个完整的值。
对齐就是空间换时间:浪费几个填充字节,换来每次访问都一步到位。
五、this 指针:永远看不见的那根线
最后的问题:不同对象调用同一个函数,函数怎么知道该访问 d1 的成员还是 d2 的成员?
cpp
Date d1, d2;
d1.Init(2026, 10, 9);
d2.Init(2026, 1, 1);
Init 函数体里只有 _year = year,没有任何区分对象的逻辑------但 d1 调它改的是 d1,d2 调它改的是 d2。答案是 C++ 给每个成员函数的形参第一个位置,藏了一个当前类类型的指针:this 指针。
编译器处理后的真实原型:
cpp
void Init(Date* const this, int year, int month, int day)
// 函数体里的 _year = year 本质是 this->_year = year
规则三条:
- 编译器自动加,实参和形参位置不能显式写 this(写了编译器会处理/报错);
- 函数体内可以显式使用 this;
- this 是
Date* const------指向不能变(你不可能让 d1 的调用操作 d2),这跟引用底层int* const是同一个设计。
所以成员函数访问成员变量,本质全部通过 this 指针 。.xxx() 和 ->xxx() 只是语法层的表现------除了函数本身,成员都可以通过指针或 . 访问。
串一下这篇的线:访问限定符给封装上了锁(class/struct 只差默认权限)→ 类域续上查找规则 → 实例化让声明变定义 → 对象只存成员变量、函数代码共用一份 → 共用的函数靠 this 认主。每个设计都是上一条的必然结果。下一篇进"中":六大默认成员函数------构造和析构,那才是 C++ 自动化的开始。