C/C++ 入门:类与对象
结构体把数据放在一起,类进一步把“数据”和“操作这些数据的函数”放在一起。
📚 基本概念速读
| 名称 | 定义 | 省流 |
|---|---|---|
| 类 | 描述一类对象的数据和行为 | 对象模板 |
| 对象 | 按类创建出来的具体实例 | 具体东西 |
| 成员变量 | 类内部保存状态的变量 | 对象的数据 |
| 成员函数 | 类内部操作对象的函数 | 对象的行为 |
| 封装 | 把数据和操作收进类里,并控制外部访问 | 管好内部状态 |
| 访问控制 | 用 public、private、protected
限制访问范围 |
谁能访问 |
| 构造函数 | 对象创建时自动调用的函数 | 初始化对象 |
| 析构函数 | 对象销毁时自动调用的函数 | 清理对象 |
| this 指针 | 成员函数里指向当前对象的指针 | 当前对象自己 |
| 拷贝构造 | 用一个已有对象创建新对象 | 复制对象 |
🧩 类与对象的基本模型
类可以理解为一种“自定义类型说明书”:它规定对象里有什么数据,以及能做什么操作。
1 | class Student { |
按这个类创建对象:
1 | Student s; |
可以这样理解:
1 | class Student |
类和对象的关系:
flowchart LR
A[class 类定义] --> B[对象 s1]
A --> C[对象 s2]
A --> D[对象 s3]
B --> E[各自保存自己的成员变量]
C --> E
D --> E
📦 封装
成员变量
成员变量保存对象自己的状态。
1 |
|
每个 Student 对象都有自己的
name、age 和 score。
1 | Student a; |
a 和 b
是两个不同对象,它们的成员变量互不影响。
成员函数
成员函数是定义在类里的函数,用来操作对象。
1 | class Student { |
调用成员函数时,要通过对象调用:
1 | Student s; |
成员函数里可以直接访问同一个对象的成员变量,所以 print()
里能直接使用
name、age、score。
对象职责
封装的重点不是“把代码塞进类里”,而是让对象对自己的状态负责。
比如分数不应该随便被设置成负数,可以把修改逻辑收进成员函数:
1 | class Student { |
这样外部不能随意破坏 score,只能通过
setScore() 修改。
🔐 访问控制
public
public 成员可以在类外部直接访问。
1 | class Student { |
如果一个成员是对象对外提供的接口,通常放在 public。
private
private 成员只能在类内部访问。
1 | class BankAccount { |
外部不能直接写:
1 | BankAccount account; |
这就是封装带来的保护:外部只能通过 deposit() 和
getBalance() 使用账户。
protected
protected
先粗略理解为:类外部不能访问,但子类可以访问。
1 | class Person { |
初学阶段如果还没学继承,不需要大量使用
protected。先记住三者区别:
| 访问控制 | 类内部 | 类外部 | 子类 |
|---|---|---|---|
public |
可以 | 可以 | 可以 |
private |
可以 | 不可以 | 不可以直接访问 |
protected |
可以 | 不可以 | 可以 |
大多数入门类会把成员变量设为
private,把稳定的操作函数设为 public。
🏗️ 构造函数
默认构造
构造函数是在对象创建时自动调用的特殊成员函数。它的名字和类名一样,没有返回类型。
1 | class Student { |
创建对象时会自动调用:
1 | Student s; |
这种不需要参数的构造函数叫默认构造函数。
带参构造
构造函数也可以带参数,让对象创建时直接拿到初始值。
1 | class Student { |
使用方式:
1 | Student s("Alice", 18); |
这比先创建对象再逐个赋值更稳,因为对象一出生就是完整状态。
初始化列表
构造函数更推荐使用初始化列表初始化成员变量。
1 | class Student { |
初始化列表写在参数列表和函数体之间:
1 | Student(...) : name(...), age(...) |
它的含义是:在构造函数体执行前,就把成员变量初始化好。
尤其遇到 const 成员或引用成员时,必须用初始化列表:
1 | class Config { |
初学阶段可以先养成习惯:构造函数优先用初始化列表设置成员变量。
🧹 析构函数
对象生命周期结束
析构函数在对象销毁时自动调用。它的名字是
~类名(),没有返回类型,也没有参数。
1 | class Logger { |
局部对象离开作用域时会被销毁:
1 | int main() |
输出顺序大致是:
1 | create |
释放资源
析构函数常用于释放对象持有的资源,比如堆内存。
1 | class Buffer { |
Buffer 对象创建时申请内存,对象销毁时释放内存。
现代 C++ 更推荐用
vector、string、智能指针等标准库类型管理资源。这里用new和delete[]是为了看清析构函数的作用。
对象生命周期可以粗略看成:
1 | 创建对象 |
👉 this 指针
成员访问
在成员函数内部,this 指向当前对象。
1 | class Student { |
这里参数也叫 name,成员变量也叫
name。this->name
明确表示当前对象的成员变量。
如果没有重名,下面两种写法通常等价:
1 | score = 90.0; |
this 的类型可以先理解成“指向当前对象的指针”。在
Student 的普通成员函数里,它大致像
Student* const this。
链式调用基础
如果成员函数返回当前对象的引用,就可以连续调用。
1 | class Student { |
使用方式:
1 | Student s; |
关键是 return *this;:this
是指针,*this 就是当前对象本身。
📋 拷贝构造
对象复制
拷贝构造函数用于“用一个已有对象创建新对象”。
1 | class Student { |
触发拷贝构造:
1 | Student a("Alice", 18); |
b 是用 a 创建出来的新对象。
默认行为
如果你不写拷贝构造函数,编译器通常会生成一个默认版本,把每个成员逐个复制。
1 | class Point { |
这类只有普通值成员的对象,默认拷贝通常没问题。
可以理解为:
1 | a.x 复制给 b.x |
资源类风险
如果对象里保存裸指针,并且析构函数会释放它,默认拷贝就可能出问题。
1 | class Buffer { |
如果发生默认拷贝:
1 | Buffer a; |
结果是 a.data 和 b.data
指向同一块堆内存。两个对象销毁时都会 delete[]
同一个地址,这就是资源类常见风险。
1 | a.data ----+ |
解决方向有三类,后面学到资源管理时会展开:
| 做法 | 说明 |
|---|---|
| 自己写正确的拷贝构造 | 复制资源本身,而不是只复制地址 |
| 禁止拷贝 | 不允许资源对象被复制 |
| 使用标准库资源类型 | 用
vector、string、智能指针减少手动管理 |
入门阶段先记住:类里有裸指针和析构函数时,要警惕默认拷贝。
⚠️ 常见误区
| 误区 | 正解 |
|---|---|
| 类只是带函数的结构体 | 类更强调封装、接口和对象职责 |
成员变量都应该 public |
需要保护内部状态时应放到 private |
| 构造函数有返回类型 | 构造函数没有返回类型,连 void 也不写 |
| 析构函数要手动调用 | 大多数情况下由对象生命周期自动触发 |
this 是当前对象本身 |
this 是指向当前对象的指针,*this
才是对象本身 |
| 默认拷贝总是安全 | 含裸指针资源的类可能出现重复释放等问题 |
✅ 总结
类把状态和行为封装到一起:构造函数负责初始化,析构函数负责清理,访问控制保护内部状态,
this表示当前对象,拷贝构造决定对象如何被复制。
学完类与对象后,再看 C++ 就不只是“函数处理数据”,而是“对象带着自己的状态和职责参与程序运行”。
Happy Hacking! 🎉