vector 底层模拟实现(上):核心机制全解析 + 迭代器失效深度剖析

🔥个人主页:K 旺仔小馒头
🍉学习方向:C/C++方向学习者
📖个人专栏:《C语言》《数据结构与算法》《C++知识分享》《C语言实战编程》《算法题从优选到贪心的解题技巧》
⭐️人生格言:“何时葡萄先熟透,你要静候再静候”

目录
前言:
一. 先看源码
二. 构造和析构
三. capacity、size
四. reserve
五. push_back
六. 迭代器
七. operator[]
八. empty
九. pop_back
十. insert
十一. erase
结尾:
前言:
C++ vector 作为最常用的动态数组容器,其高效性背后是精巧的底层设计。本文深入剖析 vector 的构造析构机制、容量管理策略及迭代器行为,通过源码解析揭示 reserve 扩容逻辑与 push_back 的实现细节,重点探讨 insert/erase 导致的迭代器失效问题及解决方案。理解这些底层原理,是写出高效、健壮 C++ 代码的关键。
一. 先看源码
那为什么string不看源码?因为string不在STL里面,string属于C++标准库
vector 源码的头文件:

这个软件(非常的方便):看源码的时候方便跳转
源码板书:

注意:模板不能分离定义到两个文件,分离定义到两个文件会导致链接错误
二. 构造和析构
知识点:
1. using 除了可以用来展开命名空间,还可以用来重定义
2. using 跟 typedef 的功能在这个部分是一样的,但是它比起 typedef 在额外的有些部分还有些更强大的功能(在C++14部分会讲到)
vector.h
namespace bit
{template <class T>class vector{public://typedef T* iterator;using iterator = T*;//C++14 using除了可以用来展开命名空间,还可以用来重定义vector() //构造:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}~vector() //析构{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}} private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
三. capacity、size
vector.h
public://typedef T* iterator;using iterator = T*;//C++14size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}
四. reserve
vector.h
public://typedef T* iterator;using iterator = T*;//C++14void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}
五. push_back
vector.h
public://typedef T* iterator;using iterator = T*;//C++14void push_back(const T& x){//满了if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;//未初始化不能直接赋值 但是不用担心,我们没有用内存池++_finish;}
六. 迭代器
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}
代码测试:
test.cpp
#include "vector.h"namespace bit
{void Print(const vector<int>& v){for (auto e : v){cout << e << " ";}cout << endl;}void test_vector1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);Print(v);for (auto e : v){cout << e << " ";}cout << endl;}
}int main()
{bit::test_vector1();return 0;
}
七. operator[]
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*;T& operator[](size_t i){assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}
代码测试:
test.cpp
#include "vector.h"namespace bit
{void Print(const vector<int>& v){for (size_t i = 0; i < v.size(); i++){//v[0]++;//这儿不可以++cout << v[i] << " ";}cout << endl;}void test_vector1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v[0]++;//这儿可以++Print(v);for (auto e : v){cout << e << " ";}cout << endl;}
}int main()
{bit::test_vector1();return 0;
}
编译:
凡是看到 没有接受const....,多半是权限放大
八. empty
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*;bool empty() const{return _start == _finish;}
九. pop_back
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*;void pop_back() //尾删{assert(!empty());--_finish;}
知识点:
1. 迭代器不一定是原生指针
2. 最底层的角度而言,始终迭代器跟指针是脱不了关系的
3. 一般情况下,插入删除可能都会导致迭代器失效(容器都会有插入删除,要小心谨慎)
4. string的insert、erase也会有迭代器失效,但是string不太关注迭代器失效的问题,因为它主要是下标来控制
总结:所有容器都会面临迭代器失效的问题(注意:迭代器失效以后就不要再使用)
那它到底失不失效,结合着使用场景,结合着底层来理解
注意:insert、erase 这两个会牵扯到迭代器失效的问题
1. 第一种最简单的pos迭代器失效是野指针
2. 迭代器失效也不一定是由于扩容导致的,也有可能是进行了erase
十. insert
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*;iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);//扩容if (_finish == _end_of_storage){//insert一定要测迭代器失效的问题size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}
代码测试:
test.cpp
namespace bit
{void Print(const vector<int>& v){for (auto e : v){cout << e << " ";}cout << endl;}void test_vector2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.push_back(5);Print(v);v.insert(v.begin(), 0);Print(v);auto it = v.begin() + 3;//insert以后,it是否失效? insert以后,it失效了,it就不能使用了v.insert(it, 30);Print(v);}
}int main()
{bit::test_vector2();return 0;
}
十一. erase
vector.h
public://typedef T* iterator;using iterator = T*;//C++14using const_iterator = const T*; iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}--_finish;return pos;}
代码测试:
test.cpp
#include "vector.h"namespace bit
{void Print(const vector<int>& v){for (auto e : v){cout << e << " ";}cout << endl;}void test_vector3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);Print(v);v.erase(v.begin());Print(v);auto it = v.begin() + 2;//it是否失效? 失效,不能访问,访问结果未定义v.erase(it);Print(v);}
}int main()
{bit::test_vector3();return 0;
}
写一段程序演示一下:(不同的平台对这个地方都不一样)

编译结果:
1. vs2022 严格检查,只要erase 以后的迭代器对象就失效了,不能访问
2. Linux下没有严格检查,也就是说有些情况对,有些情况不对,有些情况可能崩溃
归根结底,这些问题本质都是迭代器失效导致的
解决办法:erase的迭代器要重置(重新赋值)以后才能使用
test.cpp
#include "vector.h"namespace bit
{void test_vector4(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);//Print(v);for (auto e : v){cout << e << " ";}cout << endl;//删除所有的偶数//auto it = v.begin();//while (it != v.end())//{// if (*it % 2 == 0)// {// v.erase(it);//这样写是错的// }// ++it;//}//解决办法:erase的迭代器要重置(重新赋值)以后才能使用auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);//erase返回的是刚刚删除那个数据的下一个位置,相当于已经++过了}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}
}int main()
{bit::test_vector4();return 0;
}
结尾:
往期精选:
《优选算法:01 双指针巧解移动零问题》
《string 类模拟实现(收尾):传统与现代写法对比及底层机制探析》
《C++ vector核心接口全解:从构造到增删查改,一篇掌握所有常用操作》
结语:vector 的灵活性源于其动态内存管理,但也暗藏迭代器失效等陷阱。本文通过底层源码与实践案例,阐明了容量扩容的核心逻辑及迭代器失效的本质原因,强调 “erase 后立即重置迭代器” 的最佳实践。其实 STL 容器的设计都有共通的逻辑,理解 vector 的实现思路,也能为后续学习 list、map 等容器打下基础。如果在实际使用中遇到新问题,欢迎在评论区交流,咱们一起把 C++ 容器玩明白~





