类是"把数据和方法打包在一起的自定义类型"。前面九章我们用的都是现成的类型(int、double、char),本章开始自己造类型——造出来的这个类型,里面既有数据(属性),又有能操作这些数据的函数(方法)。这是全书真正的转折点:从"用别人的零件"变成"造自己的零件"。
第九章是"想清楚",本章是"写出来"。 9.1 说程序长大后会卡在"数据敞开、改动扩散";9.2.3 说药方是封装(数据藏起来、只留门);9.3.2 教了怎么用类图画出来。本章就是把那张图变成能编译的代码。 另外,第八章的 struct(8.1)就是类的雏形——你会发现 class 和 struct 的写法几乎一样,只差一个默认访问权限。所以本章没有全新的语法结构,是把已经会的零件按新方式组装。
一、先回答一个问题:为什么需要"类"
到第八章为止,我们写程序的方式是:
struct Student { // 数据:打包在一起
char name[20];
int age;
double score;
};
void printStudent(Student s) { ... } // 函数:**漂在外面**
bool isPass(Student s) { ... } // 函数:**漂在外面**
void setScore(Student &s, double v) { s.score = v; } // 函数:还在外面
问题在哪? 数据和它的函数分居两地:
| 麻烦 | 具体表现 |
|---|---|
| 找不齐 | 想看看"学生能干什么",得在几千行代码里翻那些"参数是 Student"的函数 |
| 管不住 | s.score = -999; 照样编译通过——分数的合法范围没人守(9.2.1 说的"不报错的错") |
| 改不动 | 哪天要给 score 加校验,得把所有直接改 score 的地方都找出来改 |
类的做法:把这堆东西装进同一个盒子里。
class Student {
private: // 数据藏起来(9.2.3 的"封装")
char name[20];
int age;
double score;
public: // 只留门
void print() { ... }
bool isPass() { ... }
void setScore(double v) { // 唯一的入口,规则可以写在这里
if (v >= 0 && v <= 100) score = v;
else cout << "分数不合法!" << endl;
}
};
现在:想改分数只能走 setScore(),而 setScore() 会拦下非法值。规则守住了,因为门只有一扇。
二、本章路线图
| 块 | 小节 | 要解决什么 |
|---|---|---|
| 造类型 | 10.1 / 10.1.1 / 10.1.2 / 10.1.3 / 10.1.4 | 类是什么、怎么写(声明与定义分离)、怎么把方法搬到类外、怎么用(声明对象) |
| 造的时候自动干活 | 10.2 / 10.2.1 / 10.2.2 | 构造函数:对象一出生就自动跑;复制构造函数:用一个对象造另一个 |
| 销毁的时候自动干活 | 10.3 | 析构函数:对象一消亡就自动跑(放手的活儿) |
| 类里面还有什么 | 10.4 组(六篇) | 访问成员、内联成员函数、静态成员、this 指针、嵌套类、局部类 |
| 开门通风 | 10.5 组(三篇) | 友元:给"外人"一把钥匙 |
| 防重名 | 10.6 组(五篇) | 命名空间:给名字加姓氏(把 2.1.3 的 using namespace std; 彻底说清) |
| 收束 | 10.7 | 知识地图与自测 |
一句话看全章:先会把数据和方法装进一个盒子(10.1),再学会让盒子"会自己开关机"(10.2 构造函数、10.3 析构函数),然后摸清盒子里的各种机关(10.4),最后处理"钥匙给谁"(10.5)和"重名怎么办"(10.6)。
三、类和结构体:长得像,脾气差一点
先把心放下:class 和 struct 在 C++ 里几乎是同一种东西,只是默认访问权限不同。
struct Student { ... }; |
class Student { ... }; |
|
|---|---|---|
| 默认访问权限 | public(不写也公开) |
private(不写就藏起来) |
| 习惯用法 | 只打包数据(纯记录) | 数据 + 方法(真正的对象) |
| 能不能有方法 | 能 | 能 |
| 能不能继承 | 能 | 能 |
struct A { int x; }; // x 是 public
class B { int x; }; // x 是 private!
A a; a.x = 1; // ✅ 能改
B b; b.x = 1; // ❌ 编译错误:'int B::x' is private
所以第八章的
struct之所以"门都没有",不是因为它叫 struct,而是因为我们没写private。 想给结构体加锁,写个private:一样管用。
为什么还留 struct? 因为它表示的语义不同:struct 表达"这就是一堆数据,随便看吧"(比如一个坐标点、一条配置记录);class 表达"这是个有规矩的对象,请按我开的口子来"。这是约定俗成,编译器不强制。
四、动手 Demo:把结构体升级成类
用第八章的 Student(8.1.3)当材料,体会"加一个门"带来的变化。
#include <iostream>
#include <cstring>
using namespace std;
class Student {
private: // ← 这一段是新增的"锁"
char name[20];
int age;
double score;
public: // ← 这三扇"门"是新增的
void init(const char *n, int a, double sc) {
strcpy(name, n);
age = a;
score = sc; // 类里面可以直接碰自己的私有成员
}
void setScore(double v) { // 唯一的改分入口
if (v >= 0 && v <= 100) {
score = v;
cout << "分数已更新为 " << score << endl;
} else {
cout << "分数不合法,拒绝修改:" << v << endl;
}
}
void print() const {
cout << name << " " << age << " 岁 " << score << " 分" << endl;
}
};
int main() {
Student s1;
s1.init("张三", 18, 92.5);
s1.print(); // 张三 18 岁 92.5 分
s1.setScore(88); // ✅ 走门:分数已更新为 88
s1.setScore(-10); // ❌ 走门被拦:分数不合法,拒绝修改:-10
s1.print(); // 分数还是 88,没被破坏
// s1.score = -999; // ← 把注释去掉试试:编译直接不过
return 0;
}
看到什么算成功:
张三 18 岁 92.5 分
分数已更新为 88
分数不合法,拒绝修改:-10
张三 18 岁 88 分
读结果:倒数第二行打印还是 88——非法值被 setScore 挡在门外了。这就是类给你买到的第一件东西:规则守得住。
把最后那行注释去掉,你会看到类似这样的报错:
error: 'double Student::score' is private within this context
记住 private within this context 这句话的脸——以后只要看到它,就是"你想从外面直接碰私有成员"。这不是编译器找你麻烦,是它在替你守规矩。
五、常见误区
- "
class和struct是两种不同的东西"——基本是同一种,只差默认访问权限(struct是public、class是private)。区别更多是表达意图的习惯:struct表"一堆数据"、class表"一个有规矩的对象"。 - "把成员全设成
private,再给每个成员配get/set就是封装"——等于门开得跟没墙一样(9.2.3 说过)。真正的封装是"只暴露有意义的行为":给setScore(v)而不是setScore+getScore一把梭,因为规则的落点是行为,不是字段。 - "类里写的函数必须在类里面定义"——不必。可以只留声明,实现写到类外(10.1.3 专门讲,多文件工程就这么干)。
- "类一写出来就占内存"——不占。类是图纸,画图纸不占地方;
Student s1;声明了对象才真正分配(8.1 说过同样的话)。 - "成员函数写
cout打印,说明学得对"——能跑,但类里的打印是"演示用的"。真写工程时,类一般不负责打印,只负责算;打印交给外面。这样同一个类才能同时服务于控制台程序、图形界面、网络服务。本章为了看得见结果才在类里打印,心里要有数。
自测三问(点开对答案)
1. 类和结构体最本质的区别是什么?——**默认访问权限**:`struct` 默认 `public`、`class` 默认 `private`。其余(能有方法、能继承、写法)**都一样**。 2. 类里的数据为什么要设成 `private`?——为了**让所有修改都走规定的门**(方法)。这样"分数必须在 0~100"这类规则才有唯一落点,**不会被从外面绕过**。 3. 看到报错 `'xxx' is private within this context`,说明什么?——你在**类外面直接访问了私有成员**。正确做法是通过该类公开的方法去操作它。- ☐ 10.1.1 类概述(下一节)
相关笔记
- 面向对象的特点 —— 上一章的结论:封装治"乱改"
- 统一建模语言的结构 —— 类图画好之后,本章把它变成代码
- 结构体 —— 类的雏形:把数据打包
- 命名空间 ——
std::是怎么来的(10.6 会讲透) - 🔷 C++基础 · 索引 —— 全课路线图