一、面向过程 vs 面向对象
写 C++ 类和对象之前,首要任务是完成编程思维转换。
- 面向过程(C 语言思想):核心关注点「怎么做」,也就是解决问题的步骤。数据与操作数据的函数相互分离。
我们定义结构体struct Student存储数据,再单独写void PrintStudent(Student* s)处理数据,数据与操作数据的函数是分离的,没有绑定。 - 面向对象(C++ 思想):核心关注点「谁来做」。把数据(属性)、操作数据的方法(行为)封装整合为一个整体,也就是对象。
🌰通俗例子:把大象装进冰箱
- 面向过程:打开冰箱门 → 抬大象进去 → 关上冰箱门
- 面向对象:
冰箱.开门();大象.走进();冰箱.关门()
二、类的引入与定义
1. 什么是类?
类(Class)是描述对象的蓝图 / 模板,封装对象的属性(成员变量)与行为(成员函数)。先有类,再通过类创建具体对象。
2. 两种定义方式
C++ 中struct和class都可以定义类,唯一区别:
struct:默认访问权限 publicclass:默认访问权限 private
class Student {
public:
// 成员函数:行为
void SetName(const char* name) {
// 逻辑省略
}
void Print() {
cout << _name << endl;
}
private:
// 成员变量:属性,一般私有化
char _name[20];
int _age;
}; // 类定义末尾分号不能省略!
三、访问限定符与封装
1. 三大访问限定符
用来管控类外部能否访问类内成员,是实现封装的基础:
- public 公有:类内、类外均可访问,一般对外提供接口函数;
- protected 保护:类内、派生类(子类)可以访问,类外无法访问(继承章节详解);
- private 私有:仅类内部可以访问,类外禁止访问,通常存放成员变量。
2. 封装的本质
封装不等于单纯把数据隐藏。
核心思想:隐藏内部复杂实现细节,只对外提供稳定、统一的公开接口。
类比开车:你只需要操作方向盘、油门(public 接口),不需要了解发动机火花塞如何点火(private 内部细节)。即便厂商升级发动机内部结构,只要油门、方向盘接口不变,驾驶方式完全不用改动。
四、类的作用域与实例化
1. 类作用域
类属于独立作用域。
- 成员函数写在类体内,可直接访问所有成员;
- 如果类内声明、类外定义成员函数,必须通过
::作用域解析符指明归属类。
class Student {
public:
void Print(); // 类内声明
private:
char _name[20];
};
// 类外实现,指定属于Student类
void Student::Print() {
cout << _name << endl;
}
2. 类的实例化
类仅仅是图纸,本身不分配物理内存(静态成员除外)。
通过类创建对象的过程叫做实例化,实例化之后,才会在栈 / 堆上分配内存。
int main() {
Student s1; // 栈上实例化对象
Student* s2 = new Student(); // 堆上实例化对象
return 0;
}
五、类的大小与对象内存模型
很多新手疑惑:类里面同时存在成员变量、成员函数,创建出来的对象占用多少内存?
误区纠正
不存在「对象大小 = 成员变量大小 + 成员函数大小」。
如果每个对象都存储一份函数代码,大量实例化对象时,内存会严重冗余。
对象存储模型
- 成员变量:存储在每一个对象内部;
- 成员函数:统一存放于代码段(只读区),所有同类对象共享同一份函数代码。
类大小计算规则:遵循结构体内存对齐
内存对齐目的:提升 CPU 内存读取效率。
对齐核心规则:
- 第一个成员变量偏移地址为 0;
- 其余成员对齐到「对齐数」整数倍地址;
对齐数 = min (编译器默认对齐数,当前成员大小)
VS 默认对齐数 8;Linux gcc 默认对齐数 4
- 类总大小必须是最大对齐数的整数倍;
- 类中嵌套其他类时,嵌套类对齐至自身最大对齐数倍数。
示例演示:
class A {
public:
char c; // 1字节,偏移0
int i; // 4字节,对齐到4倍数,1~3填充
};
// 总大小:8字节
class B {
public:
int i; // 4字节,偏移0
char c; // 1字节,偏移4
};
// 原始占用5字节,补齐至最大对齐数4的倍数 → 总大小8字节
特殊场景:空类
class Empty {};
空类大小为 1 字节。
C++ 标准规定:每一个对象必须拥有唯一独立地址。编译器自动插入 1 字节占位,用来区分多个不同空类对象。
六、this 指针 —— 打通对象与成员函数的关键
1. this 指针诞生的原因
成员函数只有一份,保存在代码段。当多个对象调用同一个成员函数时,函数如何区分当前操作哪一个对象的数据?
答案:this 指针
调用成员函数时,编译器自动将调用对象的地址,作为隐藏形参传递给成员函数,函数内部this指针就指向当前对象。
// 用户编写代码
s1.Print();
// 编译器底层处理伪代码
Student::Print(&s1);
2. this 指针核心特性
- 类型:
Student* const this—— 指针本身不可修改,不能指向其他对象;不加 const 时,可以修改指向对象内部成员; - 存储位置:属于函数隐式形参,存放于栈空间;
- 空指针风险:若对象指针为
nullptr,this 指针即为空。函数内不访问任何成员变量程序可能不崩溃,但属于未定义行为,工程中严禁这么写。
3. 典型应用:链式调用
函数返回*this(对象自身引用),实现连续调用:
class Counter {
public:
Counter& Add() {
_count++;
return *this;
}
private:
int _count = 0;
};
int main() {
Counter c;
c.Add().Add().Add(); // 链式连续调用
return 0;
}
全文总结
本篇完整梳理 C++ 类与对象入门核心知识点:
- 思维转变:从面向过程「步骤驱动」切换到面向对象「实体驱动」;
- 类基础:class 定义、访问限定符,理解封装是隐藏实现、暴露接口;
- 实例化与内存模型:类是模板,对象占用内存仅存放成员变量,遵循内存对齐;空类占用 1 字节;
- this 指针:编译器隐式传递对象地址,连接对象与共享的成员函数,支持链式调用。
浙公网安备 33010602011771号