🔷 第十章 类和对象
📄 一句话概括

类是"把数据和方法打包在一起的自定义类型"。前面九章我们用的都是现成的类型(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 这句话的脸——以后只要看到它,就是"你想从外面直接碰私有成员"。这不是编译器找你麻烦,是它在替你守规矩。

五、常见误区

  1. "class 和 struct 是两种不同的东西"——基本是同一种,只差默认访问权限(struct 是 public、class 是 private)。区别更多是表达意图的习惯:struct 表"一堆数据"、class 表"一个有规矩的对象"。
  2. "把成员全设成 private,再给每个成员配 get/set 就是封装"——等于门开得跟没墙一样(9.2.3 说过)。真正的封装是"只暴露有意义的行为":给 setScore(v) 而不是 setScore + getScore 一把梭,因为规则的落点是行为,不是字段。
  3. "类里写的函数必须在类里面定义"——不必。可以只留声明,实现写到类外(10.1.3 专门讲,多文件工程就这么干)。
  4. "类一写出来就占内存"——不占。类是图纸,画图纸不占地方;Student s1; 声明了对象才真正分配(8.1 说过同样的话)。
  5. "成员函数写 cout 打印,说明学得对"——能跑,但类里的打印是"演示用的"。真写工程时,类一般不负责打印,只负责算;打印交给外面。这样同一个类才能同时服务于控制台程序、图形界面、网络服务。本章为了看得见结果才在类里打印,心里要有数。
自测三问(点开对答案) 1. 类和结构体最本质的区别是什么?——**默认访问权限**:`struct` 默认 `public`、`class` 默认 `private`。其余(能有方法、能继承、写法)**都一样**。 2. 类里的数据为什么要设成 `private`?——为了**让所有修改都走规定的门**(方法)。这样"分数必须在 0~100"这类规则才有唯一落点,**不会被从外面绕过**。 3. 看到报错 `'xxx' is private within this context`,说明什么?——你在**类外面直接访问了私有成员**。正确做法是通过该类公开的方法去操作它。

相关笔记