向量:接口与实现
看完你能自己定义向量接口并写出一个可动态扩容的实现
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
向量:接口与实现
看完你能自己定义向量接口并写出一个可动态扩容的实现
什么是 ADT
家里的插座只规定两孔、220V,谁造的电器都能用;至于电从哪来、怎么发电,插座不管。ADT 就是给数据结构定这样一张「插座规格书」。
插座的孔位与电压规格对应 ADT 规定的操作集
接口 vs 实现
很多人把「向量能做什么」和「向量怎么做到」混为一谈,导致换实现就重写代码。这页把两层彻底分开。
- 回答「能做什么」:get/insert/remove 的语义
- 使用者唯一需要知道的东西
- 稳定不变,是承诺与契约
- 回答「怎么做」:用数组还是链表、扩容策略
- 使用者应该完全不关心的内部细节
- 随时可替换,代价与复杂度各异
抽象与实现的分层结构
从上往下读:依赖箭头向下;虚线是「不需要知道」的关系,这正是抽象的意义。
向量的定义
上一页我们把向量当作一个 ADT 看接口。现在打开它:你最熟悉的数组就是原型——元素排成一条线,靠位置编号访问。向量把这条线推广成真正的抽象结构。
座位号唯一确定一个座位,就像秩唯一确定一个元素
向量 ADT 操作总览
从 ADT 根节点分出读、写两大接口分支,每类下挂具体操作;先看分类,再看每个操作的归属。
ADT 模板声明
用 C++ 模板类声明展示向量接口的规范写法:声明即契约,元素类型与操作一目了然。
class 公有区是 ADT 的操作集(契约),private 区是实现细节。使用者只依赖 public 声明,实现可自由更换。
一次插入的完整过程
insert(r,e) 一次调用,底层其实走了四步,一步都省不掉。
区间删除与单元素删除
删除的本质是把后缀整体前移,覆盖被删区间。
操作实例自测
在长度为 n 的向量中,把元素 e 插入到秩为 r 的位置,插入后 e 的秩以及原来秩为 r 的元素分别是多少?
构造与析构
前几页我们调 insert、del 很顺手,但 _elem 这块数组是动态 new 出来的。既然是借来的空间,用完谁来还?这页看向量生命周期两端的「管家」:构造与析构。
构造对应签约拿房,析构对应退租交还钥匙
复制:copyFrom 实现
邓俊辉《数据结构》向量真实源码:从数组区间 [lo,hi) 逐元素复制,观察容量如何申请。
容量取 2×区间长度,为后续 append 留余量、摊还降低扩容次数;逐元素赋值使整体复杂度为 O(hi-lo)。
复制中的内存布局
沿箭头看两块内存:左为源向量,右为新建向量;对比两种复制方式的指针指向差异。
要点收束
- ✓ADT 只规定操作语义,与底层实现解耦
- ✓接口是契约,实现可替换且不影响使用者
- ✓构造析构管理生命周期,RAII 保证资源不泄漏
- ✓复制必须深拷贝,否则共享内存引发悬空指针
- ✓插入删除的边界与扩容策略决定复杂度
课后思考
三个问题请先独立想几分钟,再对照参考答案,看看思路是否一致。
参考答案关键是职责分离:区间参数描述『复制什么』,目标空间参数描述『复制到哪』。合并后向量扩容、区间拼接等场景都得各自重写,接口反而失去弹性。
参考答案向量将失去对内存的所有权:外部数组一旦被释放或修改,向量立即悬空失效。自管内存是实现『动态扩容』的前提,也是封装边界的底线。
参考答案不能。链表节点地址不连续,只能逐节点深复制,无 memcpy 式整体搬运。但『自管内存、按需分配』的原则不变——变的只是分配的粒度与方式。