🧩 第五章 理解内存结构:程序员的基本素养
📄 一句话概括

同一份数据,放全局区只有 1 份(2 个键),放堆上有 3 份——OOP 的特征就体现在这次搬家里。

📝 本节和前面几节的关系

5.5 分完三块地,这一节说 OOP 到底把东西放在了哪一块。

一、过程式放哪

数据放在模块级(静态区):一本全局字典,或者几个全局变量。 任何函数拿到名字就能改——不需要经过任何口子。 只有一份。想为第二个购物车单独存数据?没有地方放。

二、OOP 放哪

数据放在堆上的实例里:self.total、self.items。 要改只能走方法——c.add(...),绕不过去。 要几份就造几个实例。实测三个购物车各自记着自己的 total:5/8/3。

三、实测对比

全局版:共享数据里 2 个键(items/total),1 份数据。 OOP 版:模块级只有 3 个对象名,3 份独立数据。 名字的数量差不多,但能存的数据份数差三倍——这就是内存用法的差别。

四、这才是 OOP 的特征

OOP 的特征不是【有类】,是数据换了地方:从一块全局区搬进 N 块堆对象。 搬完之后【谁能改它】变了:从【所有人】变成【拿到这个对象的人】。 所以 3.9 那 480 条依赖边是被【搬家】断掉的,不是被语法断掉的。 判断一个程序是不是真 OOP,看它的数据住哪,别只看它有没有 class。

五、动手 Demo:OOP 的特征在于内存的用法

同一套购物车逻辑:一遍用一本全局字典,一遍用三个实例。 数一数各自能存几份独立数据。

GBL = {"total": 0, "items": []}    # 过程式:一本共享数据
def add_global(name, price):
    GBL["total"] += price
    GBL["items"].append(name)

class Cart:
    def __init__(self):
        self.total = 0
        self.items = []
    def add(self, name, price):
        self.total += price
        self.items.append(name)

c1, c2, c3 = Cart(), Cart(), Cart()

实跑输出:

--------------------------------------------------------------------
OOP 的特征在于内存的用法
--------------------------------------------------------------------

  过程式把数据放在模块级(静态区),**任何函数都能直接改。**
  OOP 把数据放进实例(堆),**要改只能走对象。**实测量一量:

      全局版:共享数据里几个键      2 个(items/total)
      全局版:能存几份独立数据      1 份(就这么一本)
      OOP 版:模块级只有对象名      3 个(c1/c2/c3)
      OOP 版:能存几份独立数据      3 份

      三个购物车的 total            5/8/3
      全局版的 total 只有一个       5
      每个实例自己的字段            items/total

  **这才是【OOP 的特征在于内存用法】的确切含义:**
  **数据从【一块全局区】搬进【N 块堆上的对象】,**
  **于是【谁能改它】从【所有人】变成【拿到这个对象的人】。**

实测:全局版共享数据 2 个键、只能存 1 份;OOP 版三个实例存了 3 份(5/8/3)。 数据从【一块全局区】搬进【N 块堆对象】——于是【谁能改它】从所有人变成拿到对象的人。

30 秒实验

  1. 找一个你项目里的全局字典,想想它为什么没法同时服务两个用户。
  2. 把它改成类,看调用方需要改哪些行。

常见误区

  1. 以为有 class 就是 OOP。数据还在全局区,等于没搬。
  2. 以为全局数据只是不优雅。它根本没法存第二份。
自测三问(点开对答案) 1. 过程式和 OOP 的数据各放在哪? —— 过程式放模块级(静态区)的全局数据,只有一份;OOP 放堆上的实例里,要几份造几个。 2. 实测两边差在哪? —— 全局版 2 个键、1 份数据;OOP 版 3 个对象、3 份独立数据(total 5/8/3)。 3. 为什么说 OOP 的特征是内存用法? —— 因为数据从一块全局区搬进 N 块堆对象,【谁能改它】才从所有人变成拿到对象的人。

相关笔记