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

面向对象编程(OOP)= 以"对象"为中心组织程序:先找出系统里有哪几个角色,把每个角色的数据(属性)和本事(方法)装进同一个"壳"里,再让这些角色互相配合。它最实在的好处是:改动被锁在对象内部——谁的需求变了就改谁,外面用不着跟着动。"类"是图纸,"对象"是按图纸造出来的实物。

📝 本节和前面几节的关系

上一节 9.2.1 讲清了面向过程的那条软肋(数据敞开 → 改动扩散)。本节正面回应它:对象把数据和方法打包,并给数据上一把锁。用的还是第八章的结构体打底——结构体是"只打包数据"的半成品,对象是"连本事一起打包、还上锁"的成品。本节里出现的 class 与 private,会在下一章正式展开语法,这里只要看懂"它在解决什么问题"就够了。

一、思考顺序的翻转:从"动词"回到"名词"

对比一下面对同一个需求,两种思路最先想到的东西:

面向过程 面向对象
先想什么 要干哪些事(动词) 有哪些角色(名词)
借书 ①检查证件 ②查在架 ③登记 ④改状态 ⑤打凭条 读者、图书、图书馆
打游戏 ①读按键 ②算位置 ③画画面 ④判断碰撞 玩家、敌人、子弹、地图
网店下单 ①验库存 ②扣款 ③生成订单 ④发货 用户、商品、购物车、订单、库存

面向对象的第一步,是把需求里的"名词"圈出来。 圈出来的这些名词,就是"对象"的候选。

为什么"先找名词"更扛改?因为名词(角色)比动词(流程)稳定。"借书"的流程可能从"手写登记"换成"扫二维码"(动词全变了),但"读者、图书、图书馆"这三个角色不会变。围着稳定的东西组织程序,程序自然更稳。

二、对象 = 属性 + 方法

一个对象自己带着两样东西:

叫什么 是什么 图书的例子
数据 属性(成员变量) 这个对象"现在是什么状态" 书名、作者、ISBN、是否在架上
行为 方法(成员函数) 这个对象"能做什么" 借出、归还、查位置

面向对象真正的关键动作不是"打包",而是"分派职责":

"这件事该谁干?" —— 只要一个动作主要是在改自己的数据,这个动作就应该写在自己身上。

"借出"这件事改的是书自己的状态(在架 → 已借出),所以 borrow() 写在书身上。这样一来:

这就是"改动范围收敛"的来源:因为数据和方法在同一个壳里,改内部的实现不会漏到外面去。

三、类与对象:图纸 vs 实物

这是 OOP 最基础的一对概念,务必分清:

类(class) 对象(object / 实例)
是什么 图纸 / 模板 / 定义 按图纸造出来的实物
职业 只描述"有什么属性和方法" 真正占有内存、有具体状态
数量 一般只写一份 想造多少造多少
例子 "图书"这个概念 《零基础学C++》这本具体的书
类比 月饼模子 压出来的每个月饼
类比② 建筑设计图 照图盖出的每一套房
class Book {                    // 图纸:凡是"图书",都有书名和"是否在架"
    // ...属性与方法(下一章详细讲写法)
};

Book b1;                        // 按图纸造出一个对象
Book b2;                        // 再造一个;两个对象各自独立

一句话:类是"概念",对象是"实体"。 第八章的结构体也是这个套路——struct Student 是图纸,Student s1; 才是实物(8.1 里那句"定义只是画图纸"在这里得到了呼应)。

四、对象之间靠"消息"配合

对象不是孤岛,它们互相喊话。这个"喊话"在代码里就是调用对方的方法:

读者.borrow(图书)        → 读者向图书发出"借出"的请求
   ↓
图书.borrow() 自己做:
   检查是否在架上 → 改自己的状态 → 回话"成功 / 失败"
   ↓
图书馆.record(借阅记录)  → 图书馆登记一笔

要点:

五、"谁变了就改谁"

把 9.2.1 那张"改动账"翻过来看新写法:

需求变化 面向对象怎么改
学生加"手机号" 只在 Student 类内部加一个属性;外面调用的人不受影响
新增"研究生" 用继承:研究生"是一种"学生,只写多出来的部分(9.2.3)
"借出"要加借期 只改 Book::borrow() 一个方法的内部;调用点一行不动
有人想乱改余额 private 直接挡住,编译期就报错

这就是面向对象买到的那个东西:把"变化"关进一个盒子里。 代价是——你得先想清楚"盒子里该放什么"(这就是 9.3 建模要干的活)。

六、动手 Demo:把"账户"封装起来

先用第八章的结构体 + 函数拼出一个"半成品对象",看清它还差什么:

#include <iostream>
using namespace std;

struct Account {
    double balance;                     // 数据(还敞着)
};

// 方法写在结构体外面,"和别的函数挤在一起"
void deposit(Account *a, double v)  { a->balance += v; }
void withdraw(Account *a, double v) {
    if (v > a->balance) { cout << "余额不足" << endl; return; }
    a->balance -= v;
}
void show(const Account *a)         { cout << "余额:" << a->balance << endl; }

int main() {
    Account acc;
    acc.balance = 100;

    deposit(&acc, 50);
    withdraw(&acc, 30);
    show(&acc);                          // 余额:120

    acc.balance = -9999;                 // ⚠️ 直接从外面捅进去,没人拦
    show(&acc);                          // 余额:-9999
    return 0;
}

看到什么算成功:最后两行打出 余额:-9999——数据敞开的后果。

现在对比"真正的对象写法"(下一章的语法,先看效果):

class Account {
private:                                 // ← 钥匙在这里
    double balance;

public:
    Account() : balance(0) {}            // 出生时余额为 0

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

int main() {
    Account acc;
    acc.deposit(100);
    acc.withdraw(150);                   // 余额不足 → 被挡
    acc.show();                          // 余额:100

    // acc.balance = -9999;              // ❌ 编译错误:private
    return 0;
}

三处关键差别:

结构体 + 函数版 类版
数据 敞开(acc.balance 谁都能写) 锁起来(private)
方法在哪 散在外面,靠命名(deposit)暗示"是给 Account 用的" 写在类里面,一看就知道属于谁
乱改的后果 运行时才出问题(余额成了负数) 编译期就报错

30 秒实验:把类版里 acc.balance = -9999; 的注释去掉,编译。编译器会报"balance 是私有成员,无法访问"。记住这个报错——它不是刁难你,是替你把一个将来才会爆的雷,提前到编译阶段给排掉了。这就是"封装"最直接的价值。

七、常见误区

  1. "面向对象就是多写几个 class"——不是。核心是"数据和方法绑在一起、数据被保护"。写一堆成员全公开的类,等于没封装。
  2. "类和对象是一回事"——图纸 vs 实物。class Book 只有一份定义;Book b1, b2; 是两个互相独立的对象(各自有自己的 onShelf)。
  3. "对象之间可以直接改对方的数据"——不该(也常常做不到)。对象之间应该通过方法(消息)配合:想改我的状态,请调用我的方法,不要伸手到我的口袋里。
  4. "属性越多越好"——属性是"这个对象自己的状态",就该属于它。把"全局都用得到的东西"硬塞给某个对象,是常见的坏设计(比如把"当前登录用户"塞进"图书"对象)。
  5. "先写类再想设计"——顺序反了。先想清楚有哪些角色、各自职责,再落成类(9.3 建模就是干这个)。直接开写类,最后往往得到一堆"什么都干"的怪物类。
自测三问(点开对答案) 1. 面向对象编程的第一步该做什么?——**把需求里的"名词"圈出来,找出系统中有哪些角色(对象候选)**,再想每个角色该有哪些数据、该会哪些本事。 2. 类和对象的区别是什么?——**类是图纸/模板**(描述有什么属性和方法,一般不占对象的内存),**对象是按图纸造出来的实物**(有自己的状态、占内存,可造多个)。 3. 面向对象凭什么能"把改动锁在内部"?——因为**数据和方法在同一个壳里,且数据通常被保护起来**;外部只能通过**方法(接口)**来操作,**改内部实现不会影响调用方**。

相关笔记