C++ vector深度剖析与模拟实现:探索模板的泛型应用

C++ vector深度剖析与模拟实现:探索模板的泛型应用

前引:在C++标准模板库STL中,vector是最常用的容器之一。它以动态数组的形式提供联系内存存储,支持随机访问和高效的尾部插入\删除操作。然而,其底层实现远非简单的数组封装,而是通过精妙的内存管理策略和数据结构设计,平衡了性能与灵活性。本文将深入探讨vector的底层实现原理,包括其核心数据结构、动态扩容机制、迭代器设计,以及实际应用中的注意事项~

在上一个容器 string 的模拟实现中,我们发现 string 的模拟实现有些单调,它仅仅操作字符串,通过划分空间+类实现它的各种接口功能即可,难度还比较正常,思维逻辑正确代码不是问题;今天的 vector 作为新学的一个容器,它比 string 要复杂一些,因为它可以接纳各种类型,这就需要用到我们之前学的模板,不仅仅是写一个类这么简单~下面开始今天的vector实现,正文开始!

目录

vector的核心数据结构

模板框架搭建

构造初始化

 析构函数

尾插数据 

扩容

扩容+初始化

​编辑​编辑​编辑

打印

迭代器

删除指定位置元素

插入元素在指定位置之前

迭代器失效

拷贝构造

赋值重载


vector的核心数据结构

vector 的底层实现依赖于三个关键指针(或者迭代器),它们共同管理内存空间与元素布局:

_start:指向数组的起始位置,即第一个元素的内存地址

_finish:指向最后一个元素的下一个位置,用于标记已分配但未使用的空间边界

_end_of_storage:指向当前分配内存的末尾,标记整个连续内存块的结束

 这三个指针的动态调整是 vector 实现高效内存管理的核心。例如:当插入元素空间不足时,_finish会触发扩容逻辑,分配更大的内存块并迁移数据!下面我们来通过2倍扩容来实现 vector

模板框架搭建

既然 vector 的实现是依靠模板来的,那么推理出来就是:在自定义空间中用模板实现类

namespace Seek { //vector模板 template<class T> class vector { public: //实现 private: T* _start; T* _finish; T* _end_of_storage; }; }

类模板的实例化:空间声明+模板类类型

Seek::vector<int> S1;

构造初始化

观察库里面的 Vector 我们发现在没有任何参数时capacity也是0,例如:

那么我们开始只需要将三个指针全部初始化为空就行了

//构造初始化 vector() :_start(nullptr) ,_finish(nullptr) ,_end_of_storage(nullptr) { }

 析构函数

析构不能释放空,因此需要先判断指针是否开辟了空间,然后再置空

//析构 ~vector() { //判断 assert(_start); //释放空间 delete[]_start; //置空 _start = _finish = _end_of_storage = nullptr; }

尾插数据 

首次学习 vector 实现我们以整形为主进行学习

在尾插时我们可能需要更改三个指针的位置,因此需要先计算一下:size()、capacity

原理:指针-指针=中间的元素个数(通过这样我们可以控制三个指针的移动) 

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

在尾插时需要考虑:如果空间已满 或者 _start为空。如果_start为空,那么无法使用memcpy

//尾插 void push_back(const T pc) { if (_finish == _end_of_storage) { //说明此时空间已满 或者 空间为空 size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity(); //如果_start为空,那么无法使用memcpy if (_start == nullptr) { _start = new T[newcapacity]; _finish = _start; _end_of_storage = _start + newcapacity; } else //开空间 reserve(newcapacity); } //存 *_finish = pc; _finish++; }

扩容

扩容就是 reserve ,传入指定数量的空间,reserve负责开辟,然后更新三个指针指向新空间

//扩容 void reserve(size_t newcapacity) { assert(newcapacity > 0); //记录 size_t size_tmp = size(); //开辟空间 T* tmp = new T[newcapacity]; //拷贝内容 memcpy(tmp, _start, sizeof(T) * newcapacity); //释放空间 delete[]_start; //更新指针 _start = tmp; _finish = _start + size_tmp; _end_of_storage = _start + newcapacity; }

注意:我们开辟新空间后,很容易忘记_finish的指向,而在释放原空间之前,需要标记元素个数

扩容+初始化

如果resize小于_finish,那么就保留resize及其以前的数据

如果resize大于_finish,那么就需要扩容+初始化(如果没有给初始值,调用构造函数)

//扩容+初始化 void resize(size_t n, const T& val=T()) { if (n < size()) { //直接删 while (size() > n) { _finish--; } } else { //扩容 reserve(n); while (n != size()) { *_finish = T(); _finish++; } } }

效果展示:

打印

如果实现流提取,虽然可以用友元解决,因为是模板无法识别变量类型,所以在成员函数内完成

而vector的类型太多了,如果去为它实现一个流提取是没有必要的,不向string是确定的 

数组属于连续存储的,因此直接解引用\下标就可以拿到数组元素,连续的内存存储也支持加加的

//打印 void Print()const { assert(_start); for (T* tmp = _start ; tmp != _finish ; tmp++) { cout << *tmp << " "; } cout << endl; }

效果展示:

迭代器

迭代器应该是返回有效元素的这个区间的指针指向,而不是放大到容量

这里数据的开始刚好是_start

末尾的下一个位置刚好是_finish \ _start+元素个数

//迭代器 iterator begin()const { return _start; } iterator end()const { return _start + size(); }

删除指定位置元素

可以看到删除指定位置元素的参数是迭代器类型:iterator,这里需要注意的是_finish的位置

//删除指定位置元素 void erase(iterator pos) { //判断有效性 assert(pos >= _start && pos < _finish); //挪动元素 while (pos + 1 < _finish) { *pos = *(pos + 1); pos++; } _finish--; }

效果展示:

插入元素在指定位置之前

 可看到它的位置也是根据迭代器去做参数的

//插入元素在指定位置之前 void insert(iterator pos, T tmp) { //检查范围 assert(pos > _start && pos <= _finish); //看是否需要扩容 if (_finish == _end_of_storage) { size_t newcapacity = capacity() * 2; reserve(newcapacity); } //挪动元素 iterator it = _finish; while (it >= pos) { *it = *(it - 1); it--; } *(pos - 1) = tmp; _finish++; }

迭代器失效

 迭代器失效的场景:

(1)insert 插入触发内存扩容,所有原有迭代器、指针、引用失效

(2)insert 插入未触发内存扩容,但元素位置移动,之后的迭代器失效

(3)erase 删除元素之后,被删除元素及其之后的所有迭代器失效

(4)clear 清理元素之后,所有迭代器失效

迭代器失效的原因(以下两种都会使原来的迭代器全部失效):

(1)内存的重新分配,vector是基于动态数组实现的容器,当元素超过容量时实现扩容,vector             重新分配内存(比如 insert )

(2)元素移动:即使没有内存重新分配,insert 会使插入的元素后移,erase 会使删除的元素前移

具有代表的就是 insert 和 erase 接口,迭代器失效与内部实现有关,比如STL里面的:

如果要解决可以接收 erase 的返回值获取下一个有效的迭代器,例如:

拷贝构造

拷贝构造是用一个已经存在的对象去创建+初始化另一个对象 

注意:不能使用memcpy,因为memcpy是按字节拷贝,如果是自定义类型会发生浅拷贝情况

           虽然这里的 T 是int类型,但是为了泛化使用,避免自定义类型发生浅拷贝

           对于连续的地址,可以使用下标[ ] 或者 直接解引用访问内容

//拷贝构造 vector(const vector<T>& V) { _start = _finish = _end_of_storage = nullptr; //开空间 _start = new T[V.capacity()]; //拷贝 iterator tmp = V._start; iterator it = _start; while (tmp != V._finish) { *it = *tmp; it++ ; tmp++; } _finish = _start + V.size(); _end_of_storage = _start + V.capacity(); }

效果展示:

赋值重载

我们可以通过拷贝构造出中间变量,再去交换临时变量的指向来完成初始化

//赋值运算符重载 vector<T>& operator=(const vector<T>& V1) { swap(V1); return *this; } void swap(const vector<T>& V1) { //V2是临时的,出了函数会销毁 vector<T> V2(V1); std::swap(_start, V2._start); std::swap(_finish, V2._finish); std::swap(_end_of_storage, V2._end_of_storage); }

注意:开始我们构造出的*this是空的,我们利用另一个对象V1去深拷贝出V2,再将V2的内容给交             换给*this,当出了swap函数去释放V2的时候,V2已经全部是空了,所以不要使用断言

                                                 【雾非雾】期待与你的下次相遇! 

Read more

保姆级教程:Windows Git 安装全流程,手把手带你从 0 到 1 (2025版)

保姆级教程:Windows Git 安装全流程,手把手带你从 0 到 1 (2025版)

Git 是程序员的必备工具。对于 Windows 用户来说,安装过程中的几十个英文选项往往让人头大。本教程将手把手带您走完安装流程,确保您的环境配置最优化、最符合现代开发标准。 第一步:下载安装包 1. 下载地址 * 官方网站:git-scm.com/download/win * 下载方式:推荐直接点击页面上的 "Click here to download" 或者 "Git for Windows/x64 Setup" 下载独立的 .exe 安装程序。 * 注:虽然可以用 Winget 命令行下载,但传统安装包更适合初次配置。 2. 版本选择 (x64 vs ARM64) * 绝大多数电脑(Intel/AMD

By Ne0inhk

GitLab配置与git集成实践

文章目录 * 前言 * 一、持续集成及Jenkins介绍 * 1.3 持续集成概念 * 1.4 Jenkins介绍 * 二、GitLab配置与git集成实践 * 2.1 GitLab概述 * 2.2 部署GitLab服务器 * 2.3 安装Gitlab环境 * 三、上传 若依项目 到 gitlab * 3.1 下载ruoyi项目 * 3.2 在Ruoyi目录初始化一个目录,为本地仓库添加一个远程仓库地址,名称为 origin。 * 3.3 将当前目录所有文件或目录打包在远程仓库中 * 3.4 提交 * 3.5 上传 * 总结 前言 本文主要讲了GitLab配置与git集成实践 一、持续集成及Jenkins介绍 软件开发生命周期(

By Ne0inhk

Git BASH安装教程

什么是 Git Bash? 简单来说,Git Bash 是为 Windows 系统提供的模拟 Linux 风格的 Bash 命令行环境,主要用于运行 Git 命令。Bash 是 Linux 和 macOS 用户常用的命令行工具,而 Windows 自带的命令提示符与它不兼容。因此,Git for Windows 软件包中包含了 Git Bash,让你可以在 Windows 上使用熟悉的 Bash 语法来操作 Git 和进行文件管理 第一步:下载 Git for Windows Git Bash 是 Git for Windows

By Ne0inhk
政安晨【零基础玩转开源AI项目】OpenClaw 跨平台AI助手完全使用指南:从入门到精通 (基于我这段时间在Ubuntu Linux系统上的使用经验为大家总结一下)

政安晨【零基础玩转开源AI项目】OpenClaw 跨平台AI助手完全使用指南:从入门到精通 (基于我这段时间在Ubuntu Linux系统上的使用经验为大家总结一下)

政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 【详细安装过程见我博客的上上篇文章】 目录 第一章:OpenClaw核心概念与架构 1.1 什么是OpenClaw? 1.2 OpenClaw技术架构 1.3 支持的模型 第二章:安装与配置 2.1 系统要求 2.2 快速安装(推荐) 2.3 从源码安装(开发版) 2.4 Docker安装 2.5 配置文件详解 第三章:通道配置详解 3.1 飞书配置 3.2 Telegram配置 3.

By Ne0inhk