📄 一句话概括
造 5 个实例,5 个 id、5 本字段表全不一样——对象本体一块,字段表又一块。
📝 本节和前面几节的关系
5.7 说类只有一份,这一节说实例为什么每次都要新开一块。
一、new 就是开一块
创建一个实例,就是在堆上新开一块,把字段填进去。 这块的寿命不归函数管——函数返回了它还在,只要有人指着它。 这才是【对象】和【局部变量】最根本的差别:一个在堆上活,一个在栈上随函数消失。
二、实测五次
五个实例的 id 全不一样;五个 __dict__ 的 id 也全不一样。
注意这是两块而不是一块:对象本体一块,它的字段表又一块。
所以造一个实例,往往要占两块内存——这一点在 5.12 会变成一个能省下几百字节的话题。
三、为什么不能共用
因为每个实例的数据不一样。Rect(6,4) 和 Rect(3,3) 的 w、h 不同。
共用一块就等于所有实例共享同一份数据——那就成了 5.6 说的全局变量。
所以【每次新开一块】不是浪费,是【多份数据】的唯一实现方式。
四、这解释了 5.13
每块堆都要有人收——不回收,程序跑久了就吃满。 而收的规则是【还有没有人指着它】。这就是 5.13 的正题。 三句话串起来:类一份(5.7)、实例 N 块(5.8)、没人指就收(5.13)。
五、动手 Demo:每次创建实例都会使用堆区
连造五个实例,把它们的 id 和字段表的 id 都打出来。 看是不是每块都新开。
objs = [Rect(i, 1) for i in range(5)]
for o in objs:
print(id(o), id(o.__dict__))
实跑输出:
--------------------------------------------------------------------
每次创建实例都会使用堆区
--------------------------------------------------------------------
【创建一个实例】在内存里就是**在堆上新开一块**。实测五次:
序号 实例的 id 它自己那本字段表
----------------------------------------------------
0 2349309254288 2349293360256
1 2349309258320 2349293399552
2 2349309257072 2349293392384
3 2349309256976 2349290533312
4 2349309256880 2349293357056
五块 id 全不一样吗 是
五本字段表全不一样吗 是
**注意第二列和第三列是两个不同的东西:**
**对象本体一块、它的字段表又一块**——所以造一个实例往往占两块。
**这就是 5.12 要讲的【配置不同】:字段怎么声明,占的块就不一样。**
实测:五个实例的 id、五本字段表的 id 全不一样。 对象本体一块、字段表又一块——造一个实例往往占两块,这也是 5.12 能省出几百字节的原因。
30 秒实验
- 造两个字段完全相同的实例,看它们的 id 是不是一样(不会一样)。
- 想一想:如果一个语言让相同内容的对象共用一块,会出什么问题。
常见误区
- 以为内容相同就会共用一块。对象各有各的身份,id 一定不同。
- 以为函数返回了对象就没了。堆上的东西不随函数消失。
自测三问(点开对答案)
1. 创建一个实例在内存里发生了什么? —— 在堆上新开一块,把字段填进去;这块的寿命不归函数管,只要有人指着它就活着。 2. 实测五个实例的结果是什么? —— 五个 id 全不同,五本字段表的 id 也全不同——对象本体一块、字段表又一块。 3. 为什么不能共用一块? —— 因为每个实例的数据不一样;共用一块就等于所有实例共享同一份数据,那就退回全局变量了。- ☐ 下一节(5.9 在变量中存储实例的指针)
相关笔记
- 孤立的实例由垃圾回收处理 — 谁负责收
- 根据继承的信息类型的不同,内存配置也不同 — 那本字段表能省
- 类的功能之三:创建很多个 — 造很多个在内存上是什么
- 🧩 面向对象是怎样工作的 · 索引