C++ vector容器底层深度剖析与模拟实现

C++ vector容器底层深度剖析与模拟实现

                                                                 

🔥近津薪荼:个人主页

🎬个人专栏:《c语言基础知识详解》《c++基础知识详解》

每个优秀的人,

都有一段沉默的时光,

❄️那段时光是付出了很多努力,

却得不到结果的日子,我们把它叫做扎根

⭐️祝您也祝我早日破土而出,巨木参天。


简介:本文主要以手打代码的方式来实现vector的各接口功能,带大家深入了解vector的底层原理~

目录

1 模板的使用说明

2 vector深度剖析及模拟实现

2.1 vector的成员变量

2.2 构造函数

2.2.1 指定大小和初始值的构造函数

2.2.2 迭代器范围构造函数

2.2.3 拷贝构造函数(现代写法)

2.3 赋值运算符重载

2.4 容量相关操作

2.4.1 reserve 开空间

2.4.2 用resize 调整大小

2.4.3 size和capacity

2.5 元素访问操作

2.5.1 下标运算符

2.6 修改操作

2.6.1 push_back 尾部添加元素

2.6.2 pop_back 尾部删除元素

2.6.3 insert 在指定位置插入元素

2.6.4 erase 删除指定位置元素

3 vector迭代器失效问题

3.1 insert失效问题

3.2 erase失效问题

3.3 删除所有的偶数

3.4 不同编译器对迭代器失效的检查

3.5 扩容之后,迭代器已经失效了

3.6 erase删除的迭代器如果是最后一个元素

3.7 string的迭代器失效

4 本文代码完整展示

(一)vector.h

(二)Test.c


1 模板的使用说明

在C++中,模板是实现泛型编程的重要工具,它允许我们编写与数据类型无关的代码。vector容器正是通过模板技术实现的,可以存储任意类型的数据:

template<class T> class vector { // ... 实现细节 };

这里的T是类型参数,当我们创建vector对象时,需要指定具体的类型

vector<int> intVector; // 存储int类型的vector vector<string> strVector; // 存储string类型的vector vector<double> dblVector; // 存储double类型的vector

模板的使用让vector具有了极强的通用性,可以容纳各种数据类型,从基本类型到自定义类类型。

2 vector深度剖析及模拟实现

2.1 vector的成员变量

vector底层使用动态数组实现,通过三个指针来管理内存:

private: iterator _start = nullptr; // 指向数组的开始位置 iterator _finish = nullptr; // 指向最后一个元素的下一个位置 iterator _end_of_storage = nullptr; // 指向分配内存的末尾

在这里,给了三个指针缺省值nullptr初始化。

2.2 构造函数

vector提供了多种构造函数来满足不同的初始化需求:、

2.2.1 指定大小和初始值的构造函数

vector(size_t n, const T& val = T()) { reserve(n); for (size_t i = 0; i < n; i++) { push_back(val); } } 

使用示例:

vector<int> v1(5); // 包含5个0的vector vector<int> v2(3, 10); // 包含3个10的vector vector<string> v3(2, "hello"); // 包含2个"hello"的vector

2.2.2 迭代器范围构造函数

template <class InputIterator> vector(InputIterator first, InputIterator last) { while (first != last) { push_back(*first); ++first; } }

这个构造函数是函数模板,可以接受任意类型的迭代器:

int arr[] = {1, 2, 3, 4, 5}; vector<int> v1(arr, arr + 5); // 从数组构造 vector<int> v2(v1.begin(), v1.end()); // 从另一个vector构造

2.2.3 拷贝构造函数(现代写法)

vector(const vector<T>& v) { vector<T> tmp(v.begin(), v.end()); swap(tmp); }

现代写法通过创建临时对象并交换资源,代码简洁且异常安全~

2.3 赋值运算符重载

vector<T>& operator=(vector<T> tmp) { swap(tmp); return *this; }

2.4 容量相关操作

2.4.1 reserve 开空间

void reserve(size_t n) { if (n > capacity()) { size_t sz = size(); T* tmp = new T[n]; if (_start) { for (size_t i = 0; i < sz; i++) { std::swap(tmp[i], _start[i]); } delete[] _start; } _start = tmp; _finish = _start + sz; _end_of_storage = _start + n; } }
  • 使用std::swap而不是直接赋值,这样可以高效地交换资源()
  • 如果直接使用memcpy,对于管理资源的类(如string)会导致浅拷贝问题!
  • 重新计算_finish_end_of_storage指针

2.4.2 用resize 调整大小

void resize(size_t n, T val = T()) { if (n < size()) { // 缩小size,只是移动_finish指针 _finish = _start + n; } else { reserve(n); // 可能需要扩容 while (_finish < _start + n) { *_finish = val; ++_finish; } } }

这里使用匿名对象做缺省参数。

使用示例:

vector<int> v = {1, 2, 3}; v.resize(5); // 变为 {1, 2, 3, 0, 0} v.resize(2); // 变为 {1, 2}

2.4.3 size和capacity

size_t capacity() const { return _end_of_storage - _start; } size_t size() const { return _finish - _start; }

2.5 元素访问操作

2.5.1 下标运算符

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

提供const和非const版本,支持读写访问只读访问

2.6 修改操作

2.6.1 push_back 尾部添加元素

void push_back(const T& x) { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = x; ++_finish; }

扩容策略:初始为0时分配4个元素空间,否则每次扩容为原来的2倍。

2.6.2 pop_back 尾部删除元素

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

2.6.3 insert 在指定位置插入元素

iterator insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); // 扩容 if (_finish == _end_of_storage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; } // 挪动数据 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; }

扩容后需要重新计算pos的位置,因为_start可能改变了。

2.6.4 erase 删除指定位置元素

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

3 vector迭代器失效问题

迭代器失效是使用vector时需要特别注意的问题,不当使用会导致未定义行为。

3.1 insert失效问题

当在vector中插入元素时,如果引起扩容,所有迭代器都会失效。

void test_vector2() { bit::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); auto it = v.begin() + 3; v.insert(v.begin(), 0); // 可能引起扩容 // insert以后,it失效了,不能再使用 // *it = 10; // 错误!未定义行为 }

3.2 erase失效问题

erase操作会使被删除元素及其后面所有元素的迭代器失效。

void test_vector3() { bit::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); auto it = v.begin() + 2; v.erase(v.begin()); // 删除第一个元素 // it失效了,不能访问,访问结果未定义 // cout << *it << endl; // 错误! }

3.3 删除所有的偶数

正确使用erase删除元素的模式:

void test_vector4() { bit::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); // 正确做法:接收erase的返回值 auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // erase返回下一个有效迭代器 } else { ++it; } } // 错误做法:直接递增迭代器 // auto it = v.begin(); // while (it != v.end()) // { // if (*it % 2 == 0) // { // v.erase(it); // erase后it失效 // } // ++it; // 错误:对失效迭代器递增 // } }

3.4 不同编译器对迭代器失效的检查

不同编译器对迭代器失效的检查严格程度不同:

  • Debug模式下通常会进行严格的迭代器检查
  • Release模式下可能没有检查,导致难以发现的bug
  • Linux的gcc编译器检查就会比较宽松

3.5 扩容之后,迭代器已经失效了

vector<int> v = {1, 2, 3}; auto it = v.begin(); v.push_back(4); // 可能引起扩容 // it已经失效,不能再使用

3.6 erase删除的迭代器如果是最后一个元素

vector<int> v = {1, 2, 3}; auto it = v.begin() + 2; // 指向3 it = v.erase(it); // 删除最后一个元素 // 此时it == v.end(),不能解引用

3.7 string的迭代器失效

与vector类似,string在插入、扩容操作、erase之后,迭代器也会失效。

4 本文代码完整展示

(一)vector.h

#pragma once #include <iostream> #include <assert.h> using namespace std; namespace bit { template<class T> class vector { public: typedef T* iterator; typedef const T* const_iterator; iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } // 构造函数 - 指定大小和初始值 vector(size_t n, const T& val = T()) { reserve(n); for (size_t i = 0; i < n; i++) { push_back(val); } } vector(int n, const T& val = T()) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } // 迭代器范围构造函数 template <class InputIterator> vector(InputIterator first, InputIterator last) { while (first != last) { push_back(*first); ++first; } } // 默认构造函数 vector() = default; // 拷贝构造函数(现代写法) vector(const vector<T>& v) { vector<T> tmp(v.begin(), v.end()); swap(tmp); } // 赋值运算符(现代写法) vector<T>& operator=(vector<T> tmp) { swap(tmp); return *this; } // 交换两个vector void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_end_of_storage, v._end_of_storage); } // 清空元素 void clear() { _finish = _start; } // 判断是否为空 bool empty() const { return _start == _finish; } // 预留空间 void reserve(size_t n) { if (n > capacity()) { size_t sz = size(); T* tmp = new T[n]; if (_start) { for (size_t i = 0; i < sz; i++) { std::swap(tmp[i], _start[i]); } delete[] _start; } _start = tmp; _finish = _start + sz; _end_of_storage = _start + n; } } // 调整大小 void resize(size_t n, T val = T()) { if (n < size()) { _finish = _start + n; } else { reserve(n); while (_finish < _start + n) { *_finish = val; ++_finish; } } } // 获取容量 size_t capacity() const { return _end_of_storage - _start; } // 获取元素个数 size_t size() const { return _finish - _start; } // 下标运算符 T& operator[](size_t i) { assert(i < size()); return _start[i]; } const T& operator[](size_t i) const { assert(i < size()); return _start[i]; } // 尾部添加元素 void push_back(const T& x) { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = x; ++_finish; } // 尾部删除元素 void pop_back() { assert(!empty()); --_finish; } // 在指定位置插入元素 iterator insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _end_of_storage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; } // 删除指定位置元素 iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; while (it != _finish) { *(it - 1) = *it; ++it; } --_finish; return pos; } // 析构函数 ~vector() { if (_start) { delete[] _start; _start = _finish = _end_of_storage = nullptr; } } private: iterator _start = nullptr; iterator _finish = nullptr; iterator _end_of_storage = nullptr; }; }

(二)Test.c

#include "vector.h" #include <iostream> using namespace std; namespace bit { // 打印vector内容 void Print(const vector<int>& v) { for (auto e : v) { cout << e << " "; } cout << endl; } // 测试基本操作 void test_vector1() { cout << "测试基本操作:" << endl; bit::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遍历 for (auto e : v) { cout << e << " "; } cout << endl; } // 测试insert操作 void test_vector2() { cout << "\n测试insert操作:" << endl; bit::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); Print(v); v.insert(v.begin(), 0); // 头部插入 Print(v); // 注意:insert后迭代器可能失效 auto it = v.begin() + 3; v.insert(it, 30); // 指定位置插入 Print(v); } // 测试erase操作 void test_vector3() { cout << "\n测试erase操作:" << endl; bit::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; v.erase(it); // 删除指定位置元素 Print(v); } // 测试删除特定元素(删除所有偶数) void test_vector4() { cout << "\n测试删除所有偶数:" << endl; bit::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); cout << "删除前: "; for (auto e : v) { cout << e << " "; } cout << endl; // 正确删除方法 auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // 接收erase返回值 } else { ++it; } } cout << "删除后: "; for (auto e : v) { cout << e << " "; } cout << endl; } // 测试resize操作 void test_vector5() { cout << "\n测试resize操作:" << endl; bit::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); cout << "原始vector: "; for (auto e : v) { cout << e << " "; } cout << endl; v.resize(3); // 缩小 cout << "resize(3): "; for (auto e : v) { cout << e << " "; } cout << endl; v.resize(20, 5); // 扩大并填充 cout << "resize(20, 5): "; for (auto e : v) { cout << e << " "; } cout << endl; } // 测试拷贝构造和赋值 void test_vector6() { cout << "\n测试拷贝构造和赋值:" << endl; bit::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); v1.push_back(6); cout << "v1: "; for (auto e : v1) { cout << e << " "; } cout << endl; // 拷贝构造 bit::vector<int> v2(v1); cout << "v2(拷贝v1): "; for (auto e : v2) { cout << e << " "; } cout << endl; // 赋值操作 bit::vector<int> v3 = {10, 20, 30, 40}; v1 = v3; cout << "v1(赋值后): "; for (auto e : v1) { cout << e << " "; } cout << endl; // 指定大小构造 bit::vector<int> v4(10, 1); cout << "v4(10个1): "; for (auto e : v4) { cout << e << " "; } cout << endl; bit::vector<char> v5(10, 'x'); cout << "v5(10个x): "; for (auto e : v5) { cout << e << " "; } cout << endl; } // 测试string类型vector void test_vector7() { cout << "\n测试string类型vector:" << endl; bit::vector<string> v1; v1.push_back("111111111111111111111111"); v1.push_back("222222222222222222222222"); v1.push_back("333333333333333333333333"); v1.push_back("444444444444444444444444"); v1.push_back("555555555555555555555555"); for (auto& e : v1) { cout << e << " "; } cout << endl; } } int main() { bit::test_vector1(); bit::test_vector2(); bit::test_vector3(); bit::test_vector4(); bit::test_vector5(); bit::test_vector6(); bit::test_vector7(); return 0; }

C++ vector基于动态数组实现,通过三个指针管理内存。核心风险是迭代器失效:insert可能扩容使所有迭代器失效;erase使被删位置后迭代器失效,必须通过it=erase(it)接收返回值。采用2倍扩容策略,现代写法通过swap实现安全拷贝。连续存储支持高效随机访问,但需警惕迭代器失效这一主要陷阱。

阅文辛苦啦,吃透这些不容易,休息一下把~~

我们下期再见~

Read more

在C++中 如何实现java中的Stream

文章目录 * 关于Steam * 特点: * 应用场景: * 关键特性: * 实现思路 * 接口定义 * map * flatmap * takeWhile * filter * sorted * toVector * 扩展 * 并行流 * 文件流 * 二元流 * 业务流 * 总结 * 代码demo * tips 关于Steam 举个例子 List<Integer> list =Arrays.asList(1,2,3);// map将int的流,转换为string 的流, 然后对流进行过滤,最后收集到一个listList<String> list2=list.stream().map(x -> x +"

By Ne0inhk
计算机基础知识总结(八股文总结----计算机网络、操作系统、数据库、c++、数据结构与算法)

计算机基础知识总结(八股文总结----计算机网络、操作系统、数据库、c++、数据结构与算法)

一、操作系统 0.内存管理 01.什么是虚拟内存?为什么需要虚拟内存? 虚拟内存为程序提供比实际物理内存更大的内存空间,同时提高内存管理的灵活性和系统的多任务处理能力。虚拟地址空间就是进程所能看到的内存空间,这段空间是连续的、独立的,实际地址空间则是内存上的空间,这段是所有进程共享的、有限的空间。虚拟内存就是把实际地址空间映射到虚拟地址空间的技术,这样就实现了内存隔离、内存扩展、物理内存管理、页面交换等技术。内存隔离就是每个进程都有自己的虚拟地址空间,因此一个进程无法访问另一个进程的内存。内存扩展就是虚拟内存让每个进程拥有比实际大的内存空间地址,可以处理更多的数据、更大的进程。物理内存管理,内存空间不足时把不常用的数据转移到硬盘上,释放内存,以助于更多进程使用。页面交换,进程可能会造成外部内存碎片,可能会导致内存空间不足,这时把不常用的数据交换到硬盘上,再交换回来,就能消除内存碎片,之前技术是内存分段,现在都是内存分页,一页或几页的内存交换就能解决内存不足的问题,而且效率高,内存分段的大数据在硬盘上读取速度慢。 02.什么是内存分段和分页?作用是什么? 内存分段是将一个程序

By Ne0inhk
C++ 继承入门(下):友元、静态成员与菱形继承的底层逻辑

C++ 继承入门(下):友元、静态成员与菱形继承的底层逻辑

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 前言 一. 友元 —— 友元关系不可继承   1、错误版本   2、正确版本 二. 静态成员 —— 继承体系中静态成员的共享性 三. 多继承及菱形继承问题:本质特点与解决方案   1、单继承与多继承模型   2、菱形继承:虚继承解决“数据冗余”与“二义性”     2.1 菱形继承出现的坑(解决二义性问题)     2.2 虚继承:彻底解决菱形继承问题     3、多继承中指针偏移问题 友元,静态成员,

By Ne0inhk
Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

◆ 博主名称: 晓此方-ZEEKLOG博客大家好,欢迎来到晓此方的博客。⭐️C++系列个人专栏: 主题曲:C++程序设计⭐️ 踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰 0.1概要&序論 这里是「此方」,好久不见。 今天我们要学习的是二叉搜索树。它是在普通二叉树的基础上加入特定约束,从而具备了高效的搜索能力。虽然这种结构能够支持高效的插入、删除与查找操作,但其性能背后也隐藏着潜在的 效率风险 。同时,在 key 与 key-value 两种不同的应用场景 下,二叉搜索树的设计与实现方式也会产生不同的变化。这里是「此方」。让我们现在开始吧! 前情提要,没有系统学习过一般二叉树的小伙伴直接看这篇文章可能会有些吃力,此方在这里留一个传送门:Re:从零开始的链式二叉树:建树、遍历、计数、查找、判全、销毁全链路实现与底层剖析 一,二叉搜索树的概念

By Ne0inhk