news 2026/6/1 19:11:29

de风——【从零开始学 C++】(十)vector的模拟实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
de风——【从零开始学 C++】(十)vector的模拟实现

目录

前言

一、vector 的核心结构

1.1 简介作用

1.2 【代码实现】核心结构定义

1.3 新手坑点提醒

二、默认成员函数实现

2.1 无参构造函数

简介作用

【代码实现】无参构造函数

2.2 带 n 个 val 的构造函数

简介作用

【代码实现】带 n 个 val 的构造函数

新手坑点提醒

2.3 迭代器区间构造

简介作用

【代码实现】迭代器区间构造

2.4 拷贝构造函数【面试高频考点】

简介作用

【代码实现】拷贝构造函数(深拷贝)

经典 Bug 和坑点

2.5 析构函数

简介作用

【代码实现】析构函数

2.6 赋值运算符重载【面试高频考点】

简介作用

【代码实现】传统写法

【代码实现】现代写法(推荐!)

两种写法对比总结

三、容量相关接口实现

3.1 size()、capacity()、empty()

简介作用

【代码实现】基础容量查询

3.2 reserve ()【面试高频考点】

简介作用

【代码实现】reserve 扩容函数

经典 Bug 和坑点

3.3 resize()

简介作用

【代码实现】resize 函数

四、迭代器模拟实现

4.1 简介作用

4.2 【代码实现】迭代器定义与接口

五、增删查改接口实现

5.1 push_back () 尾插

简介作用

【代码实现】push_back 尾插

5.2 pop_back () 尾删

简介作用

【代码实现】pop_back 尾删

5.3 insert () 任意位置插入【面试高频考点】

简介作用

【代码实现】insert 插入函数

5.4 erase () 删除元素【面试高频考点】

简介作用

【代码实现】erase 删除函数

迭代器失效详解

5.5 swap () 交换函数

简介作用

【代码实现】swap 交换函数

5.6 operator [] 下标访问

简介作用

【代码实现】operator [] 重载

六、常见问题与坑点总结

6.1 深浅拷贝问题

6.2 迭代器失效问题

6.3 自赋值问题

6.4 reserve 扩容的坑

结语



前言

大家好,我是你们的小小风呀!临近期末,小风不得不开始恶补学校课程了,哎~~,当初欠下的终究是要还的呀!所以小风的博客只能不定时更新啦!望大家理解,小风也不想挂课呀!言归正传 今天我们来到【从零开始学 C++】专题的第十篇,这篇可以说是 STL 容器学习的重中之重 ——vector 的模拟实现

为什么要模拟实现 vector?因为:

  • 这是面试必考题,90% 的 C++ 面试都会问到

  • 能真正理解底层内存管理机制

  • 能搞懂深浅拷贝、迭代器失效这些 "坑"

  • 为学习其他容器打下坚实基础

本文将手把手带你实现一个完整的 vector,所有代码都可以直接运行,所有面试重点我都会用【面试高频考点】标注出来。新手小白也能轻松看懂!


一、vector 的核心结构

1.1 简介作用

vector 本质上就是一个动态数组,和普通数组的区别就是:它可以自动扩容!

为了管理这个动态数组,vector 内部用了三个指针来维护:

指针名

作用

大白话理解

_start

指向数组的起始位置

数组的 "头"

_finish

指向最后一个有效元素的下一个位置

数组的 "尾巴"

_endofstorage

指向整个容量的末尾位置

数组的 "天花板"

这三个指针的关系:

  • size() = _finish - _start(有效元素个数)

  • capacity() = _endofstorage - _start(总容量大小)

  • 永远满足:_start ≤ _finish ≤ _endofstorage

1.2 【代码实现】核心结构定义

#pragma once #include <iostream> #include <assert.h> using namespace std; namespace my_vector { template<class T> class vector { public: // 后面会实现各种成员函数... private: T* _start; // 指向数组起始位置 T* _finish; // 指向最后一个有效元素的下一个位置 T* _endofstorage; // 指向整个容量的末尾 }; }

1.3 新手坑点提醒

常见错误:新手容易把_finish理解成 "指向最后一个元素",这是错的! ✅正确理解_finish指向的是最后一个元素的下一个位置,这样设计是为了方便计算 size 和判断是否为空。


二、默认成员函数实现

2.1 无参构造函数

简介作用

创建一个空的 vector,三个指针都初始化为 nullptr,表示还没有分配任何内存。

【代码实现】无参构造函数
// 无参构造函数 vector() : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) {}

2.2 带 n 个 val 的构造函数

简介作用

创建一个 vector,里面有 n 个值为 val 的元素。比如vector<int> v(5, 10)就创建了 5 个 10。

【代码实现】带 n 个 val 的构造函数
// 带n个val的构造函数 vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { // 先开n个空间 reserve(n); // 然后插入n个val for (size_t i = 0; i < n; i++) { push_back(val); } } // 为了避免int类型的匹配问题,再重载一个版本 vector(int n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
新手坑点提醒

经典 Bug:如果只写vector(size_t n, ...),当你写vector<int> v(5, 10)时,5 是 int 类型,会优先匹配迭代器区间构造,导致奇怪的错误! ✅解决方案:重载一个 int 版本的构造函数。


2.3 迭代器区间构造

简介作用

用另一个容器的迭代器区间来初始化 vector,非常灵活,可以接收各种容器的数据。

【代码实现】迭代器区间构造
// 迭代器区间构造(模板函数) template <class InputIterator> vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { // 遍历迭代器区间,一个个插入 while (first != last) { push_back(*first); first++; } }

2.4 拷贝构造函数【面试高频考点】

简介作用

用一个已有的 vector 来创建新的 vector。这里必须深拷贝,否则会出大问题!

什么是浅拷贝?就是两个对象共用同一块内存,一个释放了另一个就变成野指针了。什么是深拷贝?重新开辟一块内存,把数据完整复制过去,两个对象互不干扰。

【代码实现】拷贝构造函数(深拷贝)
// 拷贝构造函数(深拷贝) vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { // 1. 先开辟和v一样大的空间 reserve(v.capacity()); // 2. 把数据一个个拷贝过来 for (auto& e : v) { push_back(e); } }
经典 Bug 和坑点

浅拷贝的灾难:如果不写拷贝构造,编译器默认生成的是浅拷贝,会导致:

  1. 两个对象析构时释放同一块内存(double free)

  2. 一个对象修改数据会影响另一个对象

  3. 程序直接崩溃!

记住:只要涉及资源管理,必须自己写深拷贝!


2.5 析构函数

简介作用

释放 vector 占用的内存,把三个指针置空。

【代码实现】析构函数
// 析构函数 ~vector() { if (_start) { delete[] _start; _start = nullptr; _finish = nullptr; _endofstorage = nullptr; } }

2.6 赋值运算符重载【面试高频考点】

简介作用

实现v1 = v2这样的赋值操作。这里有传统写法现代写法两种,面试经常对比!

【代码实现】传统写法
// 赋值运算符重载 - 传统写法 vector<T>& operator=(const vector<T>& v) { // 1. 防止自赋值(v = v) if (this != &v) { // 2. 释放原来的空间 delete[] _start; // 3. 开辟新空间 _start = new T[v.capacity()]; _finish = _start; _endofstorage = _start + v.capacity(); // 4. 拷贝数据 for (size_t i = 0; i < v.size(); i++) { *(_finish++) = v[i]; } } return *this; }
【代码实现】现代写法(推荐!)
// 赋值运算符重载 - 现代写法(简洁又安全) vector<T>& operator=(vector<T> v) // 注意:这里是传值,不是传引用! { // 直接交换!就这么简单! swap(v); return *this; }
两种写法对比总结

对比项

传统写法

现代写法

代码量

多,容易写错

极少,3 行搞定

自赋值检查

需要手动检查

天然避免(传值已经拷贝了一份)

异常安全

可能异常(new 失败)

异常安全(拷贝失败不影响原对象)

推荐度

⭐⭐

⭐⭐⭐⭐⭐

💡现代写法原理:利用传值参数自动调用拷贝构造生成临时对象,然后交换临时对象和 this 对象的资源,临时对象出作用域自动析构 —— 完美!


三、容量相关接口实现

3.1 size()、capacity()、empty()

简介作用

这三个是最基础的查询接口:

  • size():返回有效元素个数

  • capacity():返回总容量大小

  • empty():判断是否为空

【代码实现】基础容量查询
// 返回有效元素个数 size_t size() const { return _finish - _start; } // 返回总容量 size_t capacity() const { return _endofstorage - _start; } // 判断是否为空 bool empty() const { return _start == _finish; }

3.2 reserve ()【面试高频考点】

简介作用

扩容核心函数!当空间不够时,开辟更大的空间,把旧数据移过去。

扩容策略:一般是按 2 倍扩容(VS 下是 1.5 倍,g++ 下是 2 倍)。

【代码实现】reserve 扩容函数
// 扩容函数(只扩不缩) void reserve(size_t n) { // 只有当n大于当前容量时才扩容 if (n > capacity()) { // 1. 记录原来的有效元素个数(很重要!) size_t oldSize = size(); // 2. 开辟新空间 T* tmp = new T[n]; // 3. 拷贝旧数据到新空间 if (_start) // 原来有数据才拷贝 { for (size_t i = 0; i < oldSize; i++) { tmp[i] = _start[i]; } // 4. 释放旧空间 delete[] _start; } // 5. 更新三个指针 _start = tmp; _finish = _start + oldSize; // 这里不能用size()!_start已经变了 _endofstorage = _start + n; } }
经典 Bug 和坑点

致命错误_finish = _start + size()这样写会崩溃! 因为计算size()的时候用的是新的_start减去旧的_finish,结果完全错误!

正确做法:扩容前先把oldSize保存下来!


3.3 resize()

简介作用

调整有效元素的个数:

  • 如果 n > size ():插入元素,用默认值填充

  • 如果 n <size ():删除尾部元素

  • 如果 n > capacity ():先扩容再插入

【代码实现】resize 函数
// 调整有效元素个数 void resize(size_t n, const T& val = T()) { if (n > size()) { // 情况1:插入元素,需要先检查扩容 reserve(n); while (_finish < _start + n) { *(_finish++) = val; } } else { // 情况2:删除元素,直接移动_finish指针 _finish = _start + n; } }

四、迭代器模拟实现

4.1 简介作用

vector 的迭代器本质上就是原生指针!因为 vector 的内存是连续的,指针天然支持 ++、--、* 等操作。

4.2 【代码实现】迭代器定义与接口

public: // 正向迭代器(就是原生指针) typedef T* iterator; typedef const T* const_iterator; // 普通迭代器接口 iterator begin() { return _start; } iterator end() { return _finish; } // const迭代器接口(只读) const_iterator cbegin() const { return _start; } const_iterator cend() const { return _finish; }

💡小知识:有了迭代器,范围 for 就自动支持了!因为范围 for 底层就是用迭代器实现的。


五、增删查改接口实现

5.1 push_back () 尾插

简介作用

在 vector 尾部插入一个元素,是最常用的接口。

【代码实现】push_back 尾插
// 尾插 void push_back(const T& x) { // 1. 检查是否需要扩容 if (_finish == _endofstorage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } // 2. 插入数据 *_finish = x; _finish++; }

5.2 pop_back () 尾删

简介作用

删除 vector 的最后一个元素。

【代码实现】pop_back 尾删
// 尾删 void pop_back() { // 断言:空vector不能尾删 assert(!empty()); _finish--; }

5.3 insert () 任意位置插入【面试高频考点】

简介作用

在 pos 位置插入一个元素。这里最容易出现迭代器失效问题!

【代码实现】insert 插入函数
// 在pos位置插入值为x的元素 iterator insert(iterator pos, const T& x) { // 检查pos合法性 assert(pos >= _start); assert(pos <= _finish); // 1. 检查扩容(这里会导致迭代器失效!) if (_finish == _endofstorage) { // 重点:扩容前记录pos相对于_start的偏移量 size_t len = pos - _start; size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); // 扩容后更新pos!因为_start已经变了 pos = _start + len; } // 2. 挪动数据(从后往前挪) iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; end--; } // 3. 插入数据 *pos = x; _finish++; return pos; // 返回新的迭代器,解决失效问题 }

为什么会失效?因为扩容时_start指向了新的内存,原来的 pos 还是指向旧内存,变成野指针了!

解决方案

  1. 扩容前记录pos - _start的偏移量

  2. 扩容后用_start + 偏移量重新计算 pos

  3. insert 函数返回新的迭代器给用户使用


5.4 erase () 删除元素【面试高频考点】

简介作用

删除 pos 位置的元素。同样存在迭代器失效问题!

【代码实现】erase 删除函数
// 删除pos位置的元素 iterator erase(iterator pos) { // 检查pos合法性 assert(pos >= _start); assert(pos < _finish); // 1. 挪动数据覆盖(从前往后挪) iterator it = pos + 1; while (it != _finish) { *(it - 1) = *it; it++; } _finish--; return pos; // 返回删除位置的下一个元素的迭代器 }
迭代器失效详解

经典错误

// 错误写法!erase后it失效了,再++会崩溃 for (auto it = v.begin(); it != v.end(); it++) { if (*it % 2 == 0) v.erase(it); }

正确写法

// 正确写法:用erase的返回值更新迭代器 for (auto it = v.begin(); it != v.end(); ) { if (*it % 2 == 0) it = v.erase(it); // 接收返回值 else it++; }

5.5 swap () 交换函数

简介作用

交换两个 vector 的内容,效率极高 —— 只需要交换三个指针就行!

【代码实现】swap 交换函数
// 交换两个vector void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); }

5.6 operator [] 下标访问

简介作用

像数组一样用[]访问元素,需要提供普通版本和 const 版本。

【代码实现】operator [] 重载
// 普通版本(可读可写) T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } // const版本(只读) const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; }

六、常见问题与坑点总结

6.1 深浅拷贝问题

问题表现:两个 vector 指向同一块内存,析构时 double free 崩溃。

根本原因:默认拷贝构造是浅拷贝,只复制指针不复制内容。

解决方案:自己实现深拷贝的拷贝构造和赋值重载。


6.2 迭代器失效问题

两种失效场景

  1. insert/erase 导致的失效:插入删除后,原来的迭代器指向的位置已经无效

  2. 扩容导致的失效:扩容后内存地址变了,所有旧迭代器都变成野指针

解决方案

  • 永远使用 insert/erase 的返回值来更新迭代器

  • 扩容后不要使用之前保存的迭代器


6.3 自赋值问题

问题表现v = v自己赋值给自己,传统写法中会先释放内存再拷贝,结果拷贝的是已经释放的内存。

解决方案

  • 传统写法:加上if (this != &v)判断

  • 现代写法:天然避免,推荐使用!


6.4 reserve 扩容的坑

经典错误:忘记保存 oldSize,直接用_finish = _start + size()导致指针计算错误。

记住:凡是涉及_start 变化的地方,都要先保存偏移量!


结语

恭喜你!到这里,一个完整的 vector 就实现完毕了!

回顾一下今天的【面试高频考点】:

  1. ✅ 深拷贝的实现(拷贝构造、赋值重载)

  2. ✅ 赋值重载的传统写法 vs 现代写法

  3. ✅ reserve 扩容逻辑

  4. ✅ insert/erase 的迭代器失效处理

这四个点,面试 10 次有 9 次会问到!建议大家亲手把代码敲一遍,理解才会更深刻。

下一篇我们将继续学习 list 的模拟实现,敬请期待!如果这篇文章对你有帮助,别忘了点赞收藏关注三连哦~


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/1 19:11:28

Redis 入门:为什么出现、核心原理与安装配置

文章目录 1. Redis 为什么出现&#xff1f;1.1 数据库性能瓶颈1.2 为什么需要缓存1.3 Redis 与 MySQL 2. Redis 基础概念2.1 Redis 是什么2.2 Redis 的核心特点高性能丰富的数据结构单线程模型I/O 多路复用持久化机制高可用机制 2.3 Redis 为什么快&#xff08;重点&#xff09…

作者头像 李华
网站建设 2026/6/1 19:10:21

为了随时随地控制 AI Agent,我做了一个 Web Terminal

背景&#xff1a;我只是想随时随地工作 前段时间&#xff0c;为了更好地使用小龙虾&#xff0c;我开发了一个监控龙虾的工具。当时我的想法很简单&#xff1a;能不能把所有工作都迁移到 OpenClaw 上&#xff1f;最好连开发也在上面完成。 那段时间 Copilot 还是按调用次数计费…

作者头像 李华
网站建设 2026/6/1 19:09:22

AppleRa1n激活锁绕过工具:解锁iOS设备的创新解决方案

AppleRa1n激活锁绕过工具&#xff1a;解锁iOS设备的创新解决方案 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 当你面对一台被激活锁困住的iOS设备时&#xff0c;那种无助感就像拿到了一把没有钥匙的…

作者头像 李华
网站建设 2026/6/1 19:00:13

3步解决B站缓存播放难题:m4s-converter高效方案全解析

3步解决B站缓存播放难题&#xff1a;m4s-converter高效方案全解析 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经遇到过这样的困扰&a…

作者头像 李华
网站建设 2026/6/1 19:00:11

Unlocker终极指南:如何在VMware中快速解锁macOS支持

Unlocker终极指南&#xff1a;如何在VMware中快速解锁macOS支持 【免费下载链接】unlocker VMware Workstation macOS 项目地址: https://gitcode.com/gh_mirrors/unlo/unlocker 想要在Windows或Linux电脑上体验macOS系统&#xff0c;却不想购买昂贵的苹果硬件&#xf…

作者头像 李华