05-类与对象:从“数据+函数”到抽象¶
目录页:见 C++ 小白教程目录
目标¶
- 理解类/对象:把数据与操作打包成一个整体
- 会写最基本的类:成员变量、成员函数、构造函数
- 理解
public/private的意义(封装) - 会用对象组织一段小程序的逻辑
先决知识¶
- 会写函数并理解作用域(见 02 章)
- 理解引用参数能修改外部变量(见 04 章 5)
1. 为什么需要类:让“数据”跟“规则”绑在一起¶
如果你只用函数和变量,程序一大就会出现这种问题:
- 数据到处都是,谁都能改
- 改坏了你也不知道是谁改的
- 相关操作分散在很多函数里,看不出它们属于同一个东西
类就是为了解决这个问题:
- 专业名称:类(class)与对象(object)
- 类比:类像“图纸/模具”,对象像“按图纸做出来的实体”
- 作用:把“数据(状态)+ 对数据的操作(行为)”打包,形成清晰边界
- 规则/坑点:封装不是为了“难用”,而是为了让错误更难发生
2. 一个最小的类:Counter(计数器)¶
可运行例子:
#include <iostream>
class Counter {
public:
Counter() : value_(0) {}
void add(int delta) {
value_ += delta;
}
void reset() {
value_ = 0;
}
int value() const {
return value_;
}
private:
int value_;
};
int main() {
Counter c;
c.add(3);
c.add(5);
std::cout << "value=" << c.value() << '\n';
c.reset();
std::cout << "value=" << c.value() << '\n';
return 0;
}
把这段拆开(只讲第一次出现的术语):
- 专业名称:成员变量(data member / member variable)
- 类比:对象身上的“私人物品”
- 作用:保存对象状态
- 专业名称:成员函数(member function / method)
- 类比:对象能做的“动作”
- 作用:在类内部实现对状态的操作
- 专业名称:访问控制(access control):
public/private - 类比:
public是对外公开窗口,private是内部仓库 - 作用:把“允许外界怎么用”固定下来,减少误用
- 专业名称:构造函数(constructor)
- 类比:对象出生时的“初始化流程”
- 作用:保证对象一创建就处于可用状态
- 规则/坑点:构造函数没有返回类型,名字与类名相同
value() const- 这里的
const表示:这个成员函数承诺“不修改对象状态”
3. 构造函数与初始化列表:对象出生时把东西装好¶
你看到的 Counter() : value_(0) {} 叫初始化列表:
- 专业名称:成员初始化列表(member initializer list)
- 类比:开箱时把物品按清单放到固定位置,而不是开箱后再到处塞
- 作用:更高效、更明确地初始化成员
- 规则/坑点:推荐优先用初始化列表来初始化成员
再看一个带参数的构造函数:
#include <iostream>
class Counter {
public:
Counter(int start) : value_(start) {}
void add(int delta) { value_ += delta; }
int value() const { return value_; }
private:
int value_;
};
int main() {
Counter c(10);
c.add(7);
std::cout << "value=" << c.value() << '\n';
return 0;
}
4. this 指针:成员函数怎么知道“我是谁”¶
当你写 c.add(3) 时,add 里需要知道要改的是哪个对象的 value_。
- 专业名称:
this指针(this pointer) - 类比:每个对象在执行自己的方法时,都有一个“指向自己的地址纸条”
- 作用:在成员函数内部区分“当前对象”与其他对象
- 规则/坑点:大多数时候你不需要显式写
this->,但知道它存在很重要
可运行例子(用链式调用直观看到 this 的作用):
#include <iostream>
class Counter {
public:
Counter() : value_(0) {}
Counter& add(int delta) {
value_ += delta;
return *this;
}
int value() const { return value_; }
private:
int value_;
};
int main() {
Counter c;
c.add(1).add(2).add(3);
std::cout << "value=" << c.value() << '\n';
return 0;
}
return *this; 的意思是:返回当前对象本身(引用),所以可以连着调用。
5. 一个更像“真实业务”的类:BankAccount¶
目标:外面不能随便把余额改成负数,必须走类提供的规则。
#include <iostream>
#include <string>
class BankAccount {
public:
BankAccount(std::string owner, int balance)
: owner_(owner), balance_(balance) {}
const std::string& owner() const { return owner_; }
int balance() const { return balance_; }
void deposit(int amount) {
if (amount <= 0) {
return;
}
balance_ += amount;
}
bool withdraw(int amount) {
if (amount <= 0) {
return false;
}
if (amount > balance_) {
return false;
}
balance_ -= amount;
return true;
}
private:
std::string owner_;
int balance_;
};
int main() {
BankAccount acc("alice", 100);
acc.deposit(50);
bool ok1 = acc.withdraw(120);
bool ok2 = acc.withdraw(20);
std::cout << "owner=" << acc.owner() << '\n';
std::cout << "balance=" << acc.balance() << '\n';
std::cout << "withdraw120=" << ok1 << '\n';
std::cout << "withdraw20=" << ok2 << '\n';
return 0;
}
这就是封装的价值:余额只能通过 deposit/withdraw 改,而且规则集中在一个地方。
常见坑¶
-
错误:把成员变量都设成 public
结果:外部随便改,类的规则形同虚设
正确:成员变量一般放 private,对外提供受控接口 -
错误:构造函数不初始化成员就开始用
结果:对象状态不可靠
正确:用构造函数 + 初始化列表保证“出生就可用” -
错误:在 const 成员函数里修改成员
结果:编译失败(这是好事)
正确:区分“查询函数(const)”和“修改函数(非 const)”
小练习¶
练习 1:写一个 Student 类¶
- 题目:保存
name与score,提供setScore、isPass、print - 输入/输出:在 main 里创建对象并输出信息
- 约束:score 只能是 0~100,超出范围不修改
- 提示:
isPass返回 bool(60 及格) - 目标:练成员变量封装与成员函数设计
练习 2:写一个 Rectangle 类¶
- 题目:保存宽高,提供
area、perimeter、scale(factor) - 输入/输出:创建对象,输出面积周长,缩放后再输出
- 约束:宽高不能为负
- 提示:
scale修改对象自身(非 const) - 目标:练 const 成员函数与非 const 成员函数区分
练习 3:Counter 增强版¶
- 题目:支持
add、sub、reset、value,并支持链式调用 - 输入/输出:在 main 里写一行链式调用并输出
- 约束:不允许 value 变成负数(小于 0 时保持 0)
- 提示:链式调用用
return *this; - 目标:练 this 与返回引用
练习 4:银行账户加转账¶
- 题目:给 BankAccount 增加
transferTo(BankAccount& other, int amount) - 输入/输出:创建两个账户,转账后输出两个余额
- 约束:转账失败不能改变任何一方余额
- 提示:先检查,再修改
- 目标:练“一个对象操作另一个对象”(引用参数)
小结¶
你现在应该掌握:
- 类是图纸,对象是实体;成员变量是状态,成员函数是行为
public/private用来做封装,让错误更难发生- 构造函数与初始化列表保证对象创建后就可用
const成员函数用于“只读操作”,不允许修改对象状态
下一篇会讲资源管理:你会看到“对象生命周期”如何帮我们自动管理资源,避免内存泄漏与悬空指针。