当前位置: 首页 > news >正文

深度剖析 C++ 之 vector(下)篇

前言

在 深度剖析 C++ 之 vector(上)篇-CSDN博客 中,我们系统地讲解了 std::vector 的常用接口、遍历方式、容量机制、增删查改操作、迭代器失效问题以及 OJ 实战技巧,掌握了它在使用层面的全部核心内容。

👉 但仅仅“会用”还不够。
要想真正理解 vector 的精髓,我们得“走进它的身体”,看看它到底是怎么工作的,然后——亲手实现一个属于自己的 vector 👇


在本篇文章中,我们将:

  • 深入剖析 vector 的底层内存布局

  • 讲清楚扩容、拷贝、插入删除的原理

  • 从零实现一个属于你自己的 zgx::vector

  • 剖析常见的实现 Bug 与改进点

  • 讲解迭代器与运算符的进阶实现

本篇文章比上篇更“底层”、更硬核💪
建议你准备一杯咖啡 ☕,慢慢往下看,一步步跟着实现👇


一、底层结构与内存模型

vector 本质上就是一个动态顺序表,它底层的核心就是 三根指针👇

T* _start;          // 指向已分配空间的起始位置
T* _finish;         // 指向当前有效元素的尾后位置
T* _endOfStorage;   // 指向已分配空间的尾后位置

可以用下图来形象地表示它👇

[start]------[finish]~~~~~~~~~~~~[end_of_storage]↑             ↑                     ↑|<-- 已使用 -->|<------ 预留空间 ---->|

举个例子,比如你有一个 vector<int>

zgx::vector<int> v;
v.reserve(10);

此时,假设你只放了 3 个元素👇

_start --------------------> [0][1][2][ ][ ][ ][ ][ ][ ][ ]↑         ↑                  ↑_start    _finish        _endOfStorage

各指针的意义:

  • _start:底层动态数组的首地址

  • _finish:当前元素的“尾后”,即 下一个可以插入元素的位置

  • _endOfStorage:当前这块空间的“尾后”,表示分配到哪了

这套结构正是 vector 能实现高效随机访问 + 自动扩容的根本。


1.1 capacity / size 的本质

在这个结构里:

size()     == _finish - _start
capacity() == _endOfStorage - _start

这就是为什么 capacity 通常比 size 大的原因——它提前分配了内存来减少多次扩容的开销。


1.2 扩容机制的核心逻辑

当插入元素时,如果 _finish == _endOfStorage,说明空间满了:

  1. 计算新的容量(VS 1.5 倍,g++ 2 倍)

  2. 申请一块更大的内存

  3. 将旧元素拷贝到新空间

  4. 释放旧空间

  5. 更新三根指针

你写 push_back 的时候,其实就是在不断重复这个过程:

if (_finish == _endOfStorage) {reserve(capacity == 0 ? 1 : capacity * 2);
}
*_finish = value;
++_finish;

二、构造与析构函数实现

有了三指针模型,我们就可以开始实现自己的 zgx::vector
你在 list2.0.h 里的实现非常规范,我们这里挑重点来讲:

2.1 默认构造

vector(): _start(nullptr), _finish(nullptr), _endOfStorage(nullptr)
{}

很简单,三根指针都置空。此时:

  • size = 0

  • capacity = 0


2.2 指定大小构造

vector(size_t n, const T& val = T())
{_start = new T[n];_finish = _start + n;_endOfStorage = _start + n;for (size_t i = 0; i < n; ++i) {_start[i] = val;}
}

这里我们申请 n 个元素的空间,并全部初始化为 val
这对应标准 vector 的 vector(n, val) 构造函数。


2.3 拷贝构造

vector(const vector<T>& v)
{size_t n = v.size();_start = new T[n];_finish = _start + n;_endOfStorage = _start + n;// 拷贝元素for (size_t i = 0; i < n; ++i) {_start[i] = v._start[i];}
}

这是一种“深拷贝”,每个元素都重新分配、复制一遍。
不能只拷贝指针,否则两个对象共享一块内存,析构时就会 double free 


2.4 赋值运算符(经典写法:copy-swap)

vector<T>& operator=(vector<T> v)
{swap(_start, v._start);swap(_finish, v._finish);swap(_endOfStorage, v._endOfStorage);return *this;
}

 这就是非常优雅的 拷贝-交换(copy-swap) 写法:

  • 先用传值参数生成一个副本(拷贝构造)

  • 然后交换指针

  • 离开函数时 v 析构,释放旧空间

这样写不仅简洁,还能自动处理自赋值,异常安全性也不错。


2.5 析构函数

~vector()
{delete[] _start;_start = _finish = _endOfStorage = nullptr;
}

析构时释放内存即可,vector 不需要调用元素的析构函数(因为这里只支持 T 是普通类型的情况,如果 T 是类对象,还要手动调用析构,属于进阶话题)。


小结

到目前为止,我们完成了:

  • 三指针结构的搭建

  • 各种构造方式 + 析构的实现

  • 赋值运算符的优雅写法

这是实现 vector 的“骨架”部分,也是很多同学最容易忽略掉的内存管理基础。


三、容量管理函数实现

在上篇中我们讲过,std::vector 的容量管理函数主要有:

  • size() / capacity()

  • empty()

  • reserve(n)

  • resize(n)

它们的底层本质其实就是围绕 _start_finish_endOfStorage 三根指针做指针运算和内存重分配。


3.1 size() / capacity() / empty()

这三个函数的实现非常简单,本质就是指针差:

size_t size() const {return _finish - _start;
}size_t capacity() const {return _endOfStorage - _start;
}bool empty() const {return _start == _finish;
}
  • size:返回当前有效元素个数

  • capacity:返回底层已分配空间大小

  • empty:判断 size 是否为 0

这些函数都是 O(1) 的,几乎没有开销。


3.2 reserve(n) —— 提前预留空间

reserve 是 vector 中最容易踩坑但也最重要的函数之一:

  • 如果 n <= 当前 capacity → 啥都不做

  • 如果 n > 当前 capacity → 重新分配更大的空间,拷贝原数据

来看实现:

void reserve(size_t n) {size_t oldCapacity = capacity();if (n > oldCapacity) {size_t oldSize = size();T* newStart = new T[n];// 拷贝旧数据for (size_t i = 0; i < oldSize; ++i) {newStart[i] = _start[i];}// 释放旧空间delete[] _start;// 更新三根指针_start = newStart;_finish = _start + oldSize;_endOfStorage = _start + n;}
}

核心步骤:

  1. 申请一块更大的空间

  2. 拷贝旧数据

  3. 释放旧空间

  4. 更新指针

这就是 vector 扩容的本质。


小示例

zgx::vector<int> v;
v.reserve(10);cout << v.size() << endl;     // 0
cout << v.capacity() << endl; // 10

注意:reserve 不会改变 size,只是分配空间。
很多初学者以为 reserve(10) 后就能直接 v[9] = xxx,这是错误的。


3.3 resize(n) —— 改变元素个数

resize 是对 size 的调整,而不是 capacity :

  • 如果 n < size:缩小 size,删掉多余元素

  • 如果 n > size:

    • 如果 n <= capacity:直接补默认值

    • 如果 n > capacity:先 reserve,再补

void resize(size_t n, const T& val = T()) {size_t oldSize = size();if (n <= oldSize) {_finish = _start + n;   // 相当于“砍掉”多余部分} else {if (n > capacity()) {reserve(n);}for (size_t i = oldSize; i < n; ++i) {_start[i] = val;}_finish = _start + n;}
}
  • 当 n 变小,直接移动 _finish 指针,相当于逻辑上“删掉”尾部元素

  • 当 n 变大,可能会触发扩容,然后再补元素

这个行为和标准 vector 一致:

zgx::vector<int> v(3, 1);
v.resize(5, 9);
// 结果:1 1 1 9 9v.resize(2);
// 结果:1 1

3.4 reserve vs resize 的区别

函数是否改变 size是否可能分配空间是否初始化新元素使用场景
reserve预留容量,避免频繁扩容
resize都可能改变逻辑长度

面试高频考点:

reserve(100) 后能访问 v[99] 吗?”
不能,因为 size 仍然是 0!


3.5 扩容策略(g++ VS VS)

虽然你手写的 zgx::vector 没有自动扩容倍数的策略(是根据需求直接 reserve 的),但标准库一般是:

平台扩容倍数
g++2 倍
VS1.5 倍(+1)

你在实现 push_back 时完全可以自己定策略,比如:

if (_finish == _endOfStorage) {size_t newCapacity = capacity() == 0 ? 1 : capacity() * 2;reserve(newCapacity);
}

这样你的 push_back 就和标准 vector 一致了。


小结

通过实现 sizecapacityreserveresize,我们掌握了 vector 最核心的内存管理机制:

  • size/capacity = 三根指针的差

  • reserve = 提前扩容,避免重复分配

  • resize = 调整逻辑长度,可能触发扩容

  • 扩容 = 重新申请空间 + 拷贝 + 更新指针

这也是你在实现 insert、push_back、erase 这些函数时的“基石”,后面几章都会用到这套逻辑。


四、访问与修改接口实现

在上篇我们已经详细讲过 std::vector 的常用访问与修改接口:

  • operator[]at()front()back()data()

  • push_back() / pop_back()

在你自己的 zgx::vector 里,核心其实就三个函数:

  • operator[] —— 随机访问

  • push_back —— 尾插元素

  • pop_back —— 尾删元素


4.1 operator[] —— 下标访问

下标访问本质就是指针偏移

T& operator[](size_t i) {return _start[i];
}const T& operator[](size_t i) const {return _start[i];
}

和标准 vector 一样:

  • 访问时间复杂度 O(1)

  • 不做越界检查(标准 vector 的 at() 才会检查)

示例:

zgx::vector<int> v(3, 10);
cout << v[0] << endl;  // 10v[1] = 99;
cout << v[1] << endl;  // 99

下标访问是你在刷题、写算法时最常用的接口,效率和数组几乎完全一样(因为底层就是数组)。


4.2 push_back —— 尾插元素(扩容核心)

push_back 的实现是 zgx::vector 的关键逻辑之一:

void push_back(const T& x) {if (_finish == _endOfStorage) {// 空间不够,先扩容size_t newCapacity = capacity() == 0 ? 1 : capacity() * 2;reserve(newCapacity);}*_finish = x;++_finish;
}

核心步骤:

  1. 判断是否有空间,没有就扩容(倍增策略)

  2. _finish 所在位置写入元素

  3. _finish++,表示元素数 +1


示例:连续 push_back 的扩容过程

zgx::vector<int> v;
for (int i = 0; i < 10; ++i) {v.push_back(i);cout << "size=" << v.size() << " capacity=" << v.capacity() << endl;
}

可能的输出(g++ 逻辑)

size=1 capacity=1
size=2 capacity=2
size=3 capacity=4
size=4 capacity=4
size=5 capacity=8
size=6 capacity=8
size=7 capacity=8
size=8 capacity=8
size=9 capacity=16
size=10 capacity=16

你能清楚看到 容量倍增扩容 的过程,每次扩容都会重新分配内存、拷贝旧元素,这正是标准 vector 的行为。


4.3 pop_back —— 尾删元素

void pop_back() {if (!empty()) {--_finish;}
}

非常简单,只要把 _finish 向前移一位,相当于逻辑上“删掉”最后一个元素

zgx::vector<int> v(3, 5);
v.pop_back();
// 结果:只剩两个元素

和标准 vector 一样,pop_back 不会收缩 capacity,也不会调用 delete,只是改变尾指针。


4.4 访问函数(补充)

你也可以加上 front / back / data,这在调试和算法题中很常用:

T& front() { return *_start; }
T& back()  { return *(_finish - 1); }
T* data()  { return _start; }

和标准 vector 完全一致,没什么额外开销。


小结

函数作用特点
operator[]下标访问元素O(1),不检查越界
push_back尾插空间不足时自动扩容
pop_back尾删不释放空间,时间 O(1)
front/back访问首尾元素O(1),不检查空
data获取底层数组首地址与 C 接口兼容

这几乎就是所有 vector 操作的“最小集合”,剩下的 insert / erase 都是在这个基础上做元素移动的。


五、插入与删除操作实现

除了 push_back / pop_back 这类尾部操作,vector 还提供了更通用的接口:

  • insert(iterator pos, const T& val) —— 在 pos 前插入一个元素

  • erase(iterator pos) —— 删除 pos 位置的元素

它们的实现都需要手动搬移数据(从 pos 往后移/往前移),这也是你实现的 zgx::vector 的重点部分。


5.1 insert 的基本逻辑

插入元素的核心步骤如下:

  1. 保存插入位置相对偏移
    因为可能会触发扩容,扩容后旧的迭代器会失效,所以必须先用 pos - _start 保存下标位置。

  2. 扩容检查
    如果 _finish == _endOfStorage,就按倍增策略 reserve。

  3. 确定新插入位置指针
    扩容后重新计算:pos = _start + offset

  4. 从尾部开始向后搬移元素
    为新元素腾出空间,注意要从尾往前搬,不然会覆盖数据。

  5. 插入元素,_finish++


5.2 insert 代码实现

iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);// 1 保存偏移size_t offset = pos - _start;// 2 扩容if (_finish == _endOfStorage) {size_t newCapacity = capacity() == 0 ? 1 : capacity() * 2;reserve(newCapacity);}// 3 重新计算 pospos = _start + offset;// 4 从尾部向后搬移元素iterator end = _finish;while (end != pos) {*(end) = *(end - 1);--end;}// 5 插入新元素*pos = x;++_finish;return pos;
}

这里的关键点是:

  • 使用 offset 来避免扩容导致迭代器失效;

  • 搬移时一定要从尾往前;

  • 返回新插入位置的迭代器,和标准 vector 一致。


示例

zgx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);auto it = v.begin() + 1;
v.insert(it, 99);
// 结果:1 99 2 3

插入 99 后,原本的 [1,2,3] 变成 [1,99,2,3],容量自动扩容(如有需要),尾部整体后移一格。


5.3 erase 的基本逻辑

删除元素的步骤:

  1. 检查位置合法

  2. 从 pos+1 到尾部的元素整体往前搬一位

  3. _finish--

不需要释放空间,也不需要扩容检查,删除只是逻辑层面的移动。


5.4 erase 代码实现

iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);iterator next = pos + 1;while (next != _finish) {*(next - 1) = *next;++next;}--_finish;return pos;
}

返回值与标准 vector 一致:

返回删除位置的迭代器(即“新元素”所处的位置)


示例

zgx::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);auto it = v.begin() + 1;
v.erase(it);
// 结果:1 3 4

删除 2 后,3 和 4 整体前移,size 减一,capacity 不变。


5.5 插入删除与迭代器失效

虽然我们在这篇里不单独展开迭代器失效(那是上篇的内容),但这里要提醒两点:

  1. 扩容导致迭代器整体失效
    insert 时如果发生扩容,之前所有迭代器、指针、引用都会失效。

  2. erase 导致 [pos, end) 范围内迭代器失效
    例如你删除中间元素后,后面元素位置改变,指向它们的迭代器不再有效。

这也是为什么标准 vector 的 insert/erase 都会返回一个迭代器,方便你继续操作。


小结

函数功能核心步骤
insert在 pos 前插元素保存 offset → 扩容 → 搬移 → 插入
erase删除 pos 元素搬移后续元素 → finish--

插入删除操作本质上就是偏移 + 扩容 + 搬移,逻辑上并不复杂,但实现时很容易出错,尤其是偏移保存这一步,很多初学者直接在扩容后用旧迭代器,结果就炸了。


六、实现难点与常见 Bug 剖析

虽然我们已经完整实现了 zgx::vector 的核心功能,但在实际写的过程中,初学者常常会遇到一些“玄学崩溃”、段错误、内存错误:

❌ 有时是析构时 double free
❌ 有时是 erase 迭代器崩溃
❌ 有时是插入后数据错乱
❌ 有时是拷贝构造浅拷贝

这一章我们就来总结并剖析几个最典型的 5 个易错点。


Bug 1:拷贝构造 / 赋值时浅拷贝导致 double free

错误示例

vector(const vector<T>& v): _start(v._start), _finish(v._finish), _endOfStorage(v._endOfStorage)
{}

这段代码直接把对方的指针“抄”过来了,没有重新分配空间,也没有复制元素。

结果:

  • 两个对象共享同一块内存;

  • 当它们先后析构时,就会对同一块内存 delete[] 两次,直接段错误。


修正写法

vector(const vector<T>& v) {size_t n = v.size();_start = new T[n];_finish = _start + n;_endOfStorage = _start + n;for (size_t i = 0; i < n; ++i) {_start[i] = v._start[i];}
}

必须“深拷贝”,申请新空间,复制元素。

这是最经典的浅拷贝陷阱,面试常考。


Bug 2:扩容后未重新计算 pos,insert 崩溃

错误示例

iterator insert(iterator pos, const T& x) {if (_finish == _endOfStorage) {reserve(capacity() * 2);}// 这里直接用 pos 了,pos 已经失效iterator end = _finish;while (end != pos) { /* ... */ }
}

一旦扩容发生,原来的 pos(本质是指向旧空间的指针)立刻变成野指针,继续使用就炸了。


修正写法

size_t offset = pos - _start;
if (_finish == _endOfStorage) {reserve(capacity() == 0 ? 1 : capacity() * 2);
}
pos = _start + offset;

保存 offset 是 insert 的关键一环。
这是很多初学者最容易忽略的细节之一。


Bug 3:erase 返回值没用,迭代器悬空

错误示例

auto it = v.begin();
while (it != v.end()) {if (*it == 3) {v.erase(it);++it;   // 悬空了!} else {++it;}
}

erase 删除元素时,会把后续元素整体前移,导致删除位置后的所有迭代器全部失效。
上面这段代码在 erase 后 it 就成了悬空迭代器,++it 直接炸掉。


修正写法

auto it = v.begin();
while (it != v.end()) {if (*it == 3) {it = v.erase(it);  // 正确!使用返回值} else {++it;}
}

erase 会返回“删除元素后,新的该位置迭代器”,标准 vector 也是这样设计的。
忽略返回值是非常常见的 bug!


Bug 4:reserve 忘了更新 _finish / _endOfStorage

错误示例

void reserve(size_t n) {if (n > capacity()) {T* newStart = new T[n];memcpy(newStart, _start, size()*sizeof(T));delete[] _start;_start = newStart;// 忘了更新 _finish 和 _endOfStorage}
}

这样扩容后 _finish 仍然指向旧空间,插入新元素时就直接写进了野地址,爆炸。


修正写法

void reserve(size_t n) {if (n > capacity()) {size_t oldSize = size();T* newStart = new T[n];for (size_t i = 0; i < oldSize; ++i) {newStart[i] = _start[i];}delete[] _start;_start = newStart;_finish = _start + oldSize;      // 正确!_endOfStorage = _start + n;      // 正确!}
}

扩容时三根指针都要正确更新,这是 vector 的根基。


Bug 5:operator= 写法错误,内存泄漏 or 自赋值崩溃

错误示例

vector<T>& operator=(const vector<T>& v) {if (this != &v) {delete[] _start;_start = new T[v.size()];// ...}return *this;
}

表面没问题,但一旦自己给自己赋值,就 delete 掉自己正在读的数据 → 直接炸掉。
此外,如果中途 new 抛异常,原空间已经被 delete → 泄漏 + 崩溃。


修正写法(copy-swap)

vector<T>& operator=(vector<T> v) {swap(_start, v._start);swap(_finish, v._finish);swap(_endOfStorage, v._endOfStorage);return *this;
}

非常优雅、健壮的写法:

  • 参数传值 → 自动调用拷贝构造,生成副本

  • swap → 交换指针

  • 函数结束 → v 析构,释放旧资源

这也是现代 C++ 实现容器时的常用套路。


小结

Bug 类型原因修正关键点
浅拷贝 double free拷贝构造/赋值只复制指针深拷贝
扩容后迭代器失效insert 扩容未保存 offset先保存 offset 再扩容
erase 悬空迭代器忽略返回值使用 erase 的返回值
reserve 忘更新指针扩容后没同步 _finish / _endOfStorage三指针都要更新
operator= 自赋值崩溃 / 泄漏delete 后再读 / 异常安全问题copy-swap 写法

这五个问题几乎覆盖了手写 vector 的 80% 踩坑点。
很多面试喜欢让你实现 vector,然后故意让你在这些地方“踩雷”,掌握它们,就已经非常接近标准实现了。


七、迭代器与运算符重载进阶

在前面我们已经用指针来实现了 begin()end(),这其实已经足够完成大部分功能:

iterator begin() { return _start; }
iterator end()   { return _finish; }

但如果想让 zgx::vector 更加贴近标准 vector,提升泛用性和可读性,我们还可以加上一些进阶接口:

  • const 版本的迭代器

  • 一些比较运算符(==、!=、<)

  • 输出流运算符(方便打印)

这些看起来不复杂,但非常实用!


7.1 const 迭代器

标准 vector 提供了 const 版本的迭代器,比如:

const vector<int> v = {1,2,3};
auto it = v.begin(); // const_iterator

这可以保证使用者不能通过迭代器修改容器内容。

在你目前的实现中,迭代器本质就是指针,因此只要加上 const 修饰就行:

const_iterator cbegin() const {return _start;
}const_iterator cend() const {return _finish;
}

当你的 zgx::vector 被声明为 const 时,调用 cbegin()/cend(),返回的是 const T*,不能通过迭代器修改元素:

const zgx::vector<int> v(3, 100);
for (auto it = v.cbegin(); it != v.cend(); ++it) {cout << *it << endl;  // 可以访问// *it = 10; 编译报错
}

这就是 const_iterator 的价值所在,很多 STL 算法都会优先调用 cbegin/cend 来确保容器内容不被修改。


7.2 比较运算符(==、!=、<)

标准 vector 支持直接用 ==!=< 来比较两个 vector:

vector<int> a = {1,2,3};
vector<int> b = {1,2,3};
vector<int> c = {1,3};cout << (a == b) << endl;  // true
cout << (a != c) << endl;  // true
cout << (a < c)  << endl;  // true (按字典序)

你也可以给 zgx::vector 加上类似的运算符:

7.2.1 operator== / operator!=

bool operator==(const vector<T>& v) const {if (size() != v.size()) return false;for (size_t i = 0; i < size(); ++i) {if (_start[i] != v._start[i]) return false;}return true;
}bool operator!=(const vector<T>& v) const {return !(*this == v);
}

这两者非常直接,按元素逐一比较即可。


7.2.2 operator<(字典序比较)

标准库 vector 的 < 是按字典序比较的:

vector<int> a = {1, 2, 3};
vector<int> b = {1, 3};
cout << (a < b) << endl;  // true,因为 2 < 3

你可以仿写:

bool operator<(const vector<T>& v) const {size_t minSize = size() < v.size() ? size() : v.size();for (size_t i = 0; i < minSize; ++i) {if (_start[i] < v._start[i]) return true;else if (_start[i] > v._start[i]) return false;}return size() < v.size();
}
  • 从头开始比较;

  • 第一个不同的元素决定大小;

  • 如果前面都相同,长度短的更小。

这让你的容器在 std::sortstd::map 等泛型算法中也能直接比较,非常有用。


7.3 输出流运算符 operator<<(调试神器)

手写容器调试最大的痛点就是看不到内容。
你可以加上一个 friend 运算符,让 cout << v 能直接输出。

friend std::ostream& operator<<(std::ostream& out, const vector<T>& v) {out << "[";for (size_t i = 0; i < v.size(); ++i) {out << v._start[i];if (i + 1 < v.size()) out << ", ";}out << "]";return out;
}

使用示例:

zgx::vector<int> v;
for (int i = 0; i < 5; ++i) v.push_back(i);
cout << v << endl;
// 输出:[0, 1, 2, 3, 4]

写算法时这功能非常香,直接看容器内容而不需要写 for 循环。


小结

功能作用
const_iterator保证 const 容器的只读访问,兼容 STL 算法
== / != / < 运算符支持元素级别比较与字典序比较,方便泛型算法
输出流运算符 <<快速调试容器内容,提高开发效率

这些接口虽然不是“必需的”,但能让你的 zgx::vector 真的像一个“正规军”,而不是只能在 main 函数里 for 循环的玩具容器。


八、实现功能回顾与总结

到这里,我们的 zgx::vector 已经从零实现完毕。
从最底层的内存结构到 insert / erase,再到迭代器与运算符重载,基本已经涵盖了 std::vector 的主体功能:

namespace zgx {template<class T>class vector {public:// 构造 / 析构 / 拷贝 / 赋值vector();vector(size_t n, const T& val = T());vector(const vector<T>& v);vector<T>& operator=(vector<T> v);~vector();// 迭代器iterator begin();iterator end();const_iterator cbegin() const;const_iterator cend() const;// 容量管理size_t size() const;size_t capacity() const;bool empty() const;void reserve(size_t n);void resize(size_t n, const T& val = T());// 访问与修改T& operator[](size_t i);void push_back(const T& x);void pop_back();T& front();T& back();T* data();// 插入与删除iterator insert(iterator pos, const T& x);iterator erase(iterator pos);// 运算符bool operator==(const vector<T>& v) const;bool operator!=(const vector<T>& v) const;bool operator<(const vector<T>& v) const;friend std::ostream& operator<<(std::ostream& out, const vector<T>& v) { ... }private:T* _start;T* _finish;T* _endOfStorage;};
}

8.1 我们已经实现了哪些?

1. 内存模型

  • 三指针 _start / _finish / _endOfStorage

  • 扩容逻辑、拷贝搬移机制

2. 常用接口

  • 构造、拷贝、赋值、析构

  • size、capacity、reserve、resize

  • push_back、pop_back、insert、erase

3. 迭代器与语法糖

  • iterator / const_iterator

  • ==、!=、< 比较

  • 输出流打印

4. Bug 剖析

  • 浅拷贝、迭代器失效、erase 返回值、reserve 忘更新指针、自赋值问题等

这些内容已经足够支撑大多数实际场景、OJ 题、甚至是一些面试笔试的“手写容器题”。


8.2 和标准 vector 相比,还有哪些没实现?

虽然我们的 zgx::vector 已经很完整,但标准库 vector 还有一些更复杂的部分:

特性标准 vectorzgx::vector
异常安全强保证(通过 RAII)无,new 失败可能内存泄漏
分配器(Allocator)可自定义内存分配策略固定使用 new/delete
移动语义(C++11)完整支持,避免多余拷贝未实现
emplace 系列(C++11)原地构造,性能更好未实现
assign / swap 等接口支持多种赋值与交换部分支持(copy-swap)
异常抛出规范明确 noexcept / strong无声明
iterator_traits / Reverse Iterator完整支持迭代器只是裸指针

换句话说,我们实现的是一个“教学版本”的 vector,清晰、好理解、便于踩坑与讲解,但离真正的工业级实现还有距离!!


8.3 写在最后

实现一个 vector 的过程,其实就是完整走了一遍“容器设计”的底层逻辑:

  • 内存管理

  • 构造/析构与拷贝语义

  • 容量管理策略

  • 元素访问与增删改

  • 迭代器与算法兼容

  • 常见 Bug 与异常安全问题

这个过程非常锻炼功底,也是 C++ 面试中最有含金量的部分之一。
当你能自己手撕一个 vector,你不仅仅是“学会了 vector”:

你真正掌握了:

  • 动态数组的实现原理

  • C++ 对象生命周期管理

  • STL 的接口设计思想

  • 内存与迭代器失效问题


总结

至此,我们完成了《深度剖析 C++ 之 vector》系列的下篇内容:

  • 底层内存结构剖析

  • 构造/析构、容量管理、增删改查接口实现

  • 插入删除的偏移与扩容机制

  • 典型 Bug 剖析与修正

  • 迭代器与运算符进阶

  • 全面回顾与设计取舍

这篇文章不只是“实现一个容器”,更重要的是通过实现,真正理解了标准 vector 的设计精髓

希望这一系列文章,能帮助你在 C++ 的道路上打下更扎实的基础
如果你坚持到这里,恭喜你完成了一个非常硬核的主题!

http://www.dtcms.com/a/474587.html

相关文章:

  • Vue计算属性与监视
  • 零基础学AI大模型之解析器PydanticOutputParser
  • Linux 命令 —— 常用命令总结
  • 【AI论文】大型推理模型能从有缺陷的思维中更好地习得对齐能力
  • 网站服务器费用wordpress手赚推广
  • 24ICPC昆明站补题
  • 口碑好的聊城网站建设设计软件网站
  • 五种编程语言比较选择最适合您项目的工具
  • 商城网站开发项目分工公司网页背景图
  • 第六章:并发编程—Go的杀手锏
  • 网站建设内部流程图定制开发网站 推广
  • 衡石科技HENGSHI SENSE 6.0:重塑企业级BI的技术范式
  • 西安便宜网站建设品牌网十大品牌排行榜
  • OpenSIPS call_center 模块测试
  • 深度学习周报(10.6~10.12)
  • 易语言实现多文件选择对话框模块详解
  • 电子商务网站建设与综合实践如何翻译wordpress主题
  • Java基础--集合复习知识点
  • spdlog讲解
  • 怎样用vps做网站超级优化
  • 下载接口返回的数据流格式文件
  • 关于网站建设的合同范本正规太原软件开发公司有哪些
  • Python反射机制通俗详解(新手友好版)
  • 网站开发要源码多少钱wordpress 静态资源加速
  • 【多线程】阻塞等待(Blocking Wait)(以Java为例)
  • 公众号做 视频网站商品行情软件下载
  • Kubernetes环境下Nginx代理Nacos服务请求故障诊断
  • Linux 文件权限详解与实操命令
  • 1Docker镜像与容器,目录挂载和卷映射的选择
  • 06_k8s数据持久化