类和对象上-class与struct实例化内存对齐与this指针

类和对象(上):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

规则三条:

  1. 编译器自动加,实参和形参位置不能显式写 this(写了编译器会处理/报错);
  2. 函数体内可以显式使用 this;
  3. this 是 Date* const------指向不能变(你不可能让 d1 的调用操作 d2),这跟引用底层 int* const 是同一个设计。

所以成员函数访问成员变量,本质全部通过 this 指针 。.xxx() 和 ->xxx() 只是语法层的表现------除了函数本身,成员都可以通过指针或 . 访问。


串一下这篇的线:访问限定符给封装上了锁(class/struct 只差默认权限)→ 类域续上查找规则 → 实例化让声明变定义 → 对象只存成员变量、函数代码共用一份 → 共用的函数靠 this 认主。每个设计都是上一条的必然结果。下一篇进"中":六大默认成员函数------构造和析构,那才是 C++ 自动化的开始。

相关推荐
C++ 老炮儿的技术栈1 小时前
C++ const 的3个核心作用
服务器·c语言·c++·算法·c
小黄人软件1 小时前
粘贴助手C++版 支持 Windows 7 / 10 / 11 不会出现缺少api-ms-win-core-path-11-1-0.dll
开发语言·c++·windows
有毒的教程1 小时前
Dev-C++ 避免爆栈(Stack Overflow)完整方案
c++·算法·深度优先
程序员老陆1 小时前
从汇编层面理解 C++ 多线程
开发语言·c++·程序设计
IOT-Power2 小时前
模板方法 + 策略模式
开发语言·c++
fpcc11 小时前
c++编程实践—堆和栈越界调试
c++
weiabc12 小时前
MSYS2 UCRT64 + g++ C++ 中文输出乱码 完整全过程
c++
灯澜忆梦13 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
aqiu11111114 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode