🔷 第九章 面向对象编程基础
📄 一句话概括

面向过程和面向对象,是组织程序的两种方式:一个按步骤组织(时间轴视角),一个按角色组织(谁负责什么)。它们不是"旧"和"新"的关系,而是"小"和"大"的关系——程序小、流程清楚时,面向过程又快又直白;程序大、变化多时,面向对象能扛住改动。本节把两种方式摆在一起对比,为下面三篇分述铺垫。

📝 本节和前面几节的关系

9.1 说了"要换一副眼镜看程序",本节把这副眼镜和旧眼镜并排放在一起看:同一个需求,两种写法各是什么样、各有什么代价。接下来三篇就是本节的展开:9.2.1 单独讲面向过程、9.2.2 单独讲面向对象、9.2.3 讲面向对象的三大特点(封装/继承/多态)。前面八章写的所有代码,都是面向过程的例子——所以本节其实是"回看自己走过的路"。

一、同一个需求,两种看法

需求:图书馆借书——读者拿着书到柜台,管理员登记,读者带走。

面向过程的写法(把注意力放在"流程"上):

① 检查读者证是否有效
② 检查这本书是否在架上
③ 在借阅记录里加一条
④ 把书的状态改成"已借出"
⑤ 打印一张凭条

一眼看清"做了哪几步",像个流程图。

面向对象的写法(把注意力放在"角色"上):

读者对象:能出示证件、能借书
图书对象:知道自己在不在架上、能改自己的状态
图书馆对象:能登记借阅记录

重点是"系统里有哪些角色、各自负责什么",流程从角色之间的配合中自然产生。

分辨口诀:面向过程画出来的是一张流程图;面向对象画出来的是一张关系图(谁认识谁)。

二、两种组织方式的根本差异

面向过程 面向对象
组织中心 步骤 / 函数 对象(数据 + 方法)
数据与方法的关系 两分离:数据是素材,函数是工序 绑在一起:数据和方法属于同一个对象
数据的可见性 敞开,谁都能改 藏起来,只能通过对象自己的方法改
需求变了怎么办 顺着流程改,改动容易扩散 谁变了就改谁,改动范围收敛
思考顺序 先想"第一步做什么" 先想"有哪些角色"
适合的场景 流程清晰、变化少、规模小 角色多、变化多、规模大
典型代表 C、早期的程序、算法题、脚本 C++、Java、C#、Python 的 OOP 部分

最要紧的一行:面向过程以"动作"为第一视角(先做什么),面向对象以"角色"为第一视角(谁来做)。

三、为什么面向对象能扛住改动

这是两种思路最实际的差别,用 9.1 那个"学生成绩管理"的例子算一笔账:

变化 面向过程的代价 面向对象的代价
学生加一个"手机号" 结构体改 1 处,但所有直接操作学生数据的函数都要复查(可能十几处) 只改"学生"这个对象内部,外面调用的人不用动
新增"研究生"类型 复制一份结构体 + 复制一批函数 用继承:研究生"是一种"学生,只写多出来的部分(9.2.3)
换一种排序算法 找到所有调用点,逐个确认 换掉对象内部的方法实现,外面的调用方式不变

核心原理:面向对象把"变化的范围"锁在了对象内部。就像一个电视机——你想换屏幕,不需要把家里的墙拆了。 因为电视机自己封装了"显示"这件事,你只通过遥控器(对外接口)跟它打交道。

四、别理解成"新打败旧"

这是零基础最容易走偏的地方,必须说清楚:

选择的标准不是"哪个高级",而是"这个程序会长多大、会变多少次"。

五、本节之后三篇的分工

小节 讲什么
9.2.1 面向过程编程 单独看它:核心画面、优势、软肋、什么时候正合适
9.2.2 面向对象编程 单独看它:对象怎么来、类与对象的关系、消息怎么传
9.2.3 面向对象的特点 封装 / 继承 / 多态——三大特点各自解决什么问题

六、动手 Demo:同一件事,两种组织

没有新语法,用"伪代码"把两种组织方式的骨架各写一遍,重点是看"数据和方法的位置"。

面向过程版(数据和函数各站一队):

// —— 数据 ——
double balance;                 // 账户余额(谁都能碰)

// —— 工序 ——
void deposit(double amount)  { balance += amount; }        // 存
void withdraw(double amount) { balance -= amount; }        // 取
void show()                  { cout << balance << endl; }  // 查

int main() {
    balance = 100;
    deposit(50);
    withdraw(30);
    show();                     // 120
    balance = -9999;            // ⚠️ 没人拦得住这一行
    show();                     // -9999(余额成了负数)
    return 0;
}

面向对象版(数据和本事装进同一个"账户"里,余额不给外人碰):

class Account {                 // 先看得懂就好,下一章正式学
private:
    double balance;             // 余额藏起来(外人不能直接改)

public:
    void deposit(double amount)  { balance += amount; }
    void withdraw(double amount) {
        if (amount > balance) { cout << "余额不足" << endl; return; }
        balance -= amount;
    }
    void show() { cout << balance << endl; }
};

int main() {
    Account a;
    a.deposit(100);
    a.withdraw(150);            // 余额不足 → 被挡住了
    a.show();                   // 100
    // a.balance = -9999;       // ❌ 编译不过:private 不许碰
    return 0;
}

看到什么算成功:面向过程版最后打出 -9999(数据敞开的后果);面向对象版打出 余额不足 然后 100(数据被保护住了)。

30 秒实验:把面向对象版里那句 a.balance = -9999; 前面的 // 去掉,重新编译——编译器直接报错(大意是 balance 是私有成员,不能访问)。这个报错不是坏事,是保护:它把"可能出错的写法"挡在了编译阶段。面向对象的第一大好处,就是"让错误尽早暴露"。

七、常见误区

  1. "面向对象就是把函数塞进结构体里"——形式像,但关键在"数据有没有被保护"。如果成员全是公开的、外面想改就改,那只是"换了个摆放方式",没拿到面向对象最核心的好处。
  2. "面向过程已经过时了"——没有。操作系统内核、嵌入式、算法实现、脚本工具,大量场景依然首选面向过程。它是"就事论事"的正路。
  3. "用了 class 就是面向对象"——不一定。写了一堆 class,但方法全在操作外面的全局数据、成员全公开,那只是"穿着对象外衣的面向过程"。判断标准是"数据和方法有没有绑在一起、数据有没有被保护"。
  4. "面向对象会让程序更快"——恰恰相反,它带来的是"好维护"不是"跑得快"(甚至有一点点额外开销)。要拼极致速度的地方,反而是面向过程/底层写法的主场。
自测三问(点开对答案) 1. 面向过程和面向对象的"第一视角"分别是什么?——面向过程是**动作**(先做什么、再做什么);面向对象是**角色**(谁负责什么、谁跟谁配合)。 2. 为什么说面向对象"能扛住改动"?——因为它把**数据和操作数据的方法绑在对象内部**,变化被**锁在对象里**;对外只留接口。**改内部不影响外面的调用者。** 3. 小工具程序该选哪种写法?——**往往面向过程更合适**。选择标准是"程序会长多大、会变多少次";小、稳、流程清楚的需求,一个函数比一个类更直白。

相关笔记