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

【C++】vector容器实现

目录

一、vector的成员变量

二、vector手动实现

(1)构造

(2)析构

(3)尾插

(4)扩容

(5)[ ]运算符重载

5.1 迭代器的实现:

(6)尾删

(7)插入

(8)删除

(9)迭代器失效

9.1 reserve扩容迭代器失效

9.2 insert后迭代器失效

9.3 erase迭代器失效

(10)resize初始化

(11)普通拷贝构造

(12)=运算符重载拷贝构造

三、其他构造方法

(一)initializer_list初始化

(二)迭代器初始化

四、动态二维数组


vector的开始也代表STL学习的开始。接下来将讲解如何手动实现部分vector常用接口。

希望在看下面文章之前已经对string类的实现比较了解,或者看过我之前描述string类实现的文章,这样对理解vector会比较友好。

正文:

一、vector的成员变量

vector学习时一定要学会查看文档https://cplusplus.com/reference/vector/vector/,vector在实际中非常的重要,我们熟悉常见的接口就可以。

下面就不带大家看整个vector源码了,只截取了它成员变量在底层如何声明的小部分原码

下图可知vector原码核心成员变量就三个迭代器,迭代器跳转过去其实也是个typedef原生指针(和string类似)所以三个变量就是三个指针。再去看构造函数,它把三个指针初始化成空(不展示),那么之前数组都有大小,vector没有直接给出就进一步去看原码怎么算大小和容量的(不展示)。下面实现就模仿库的形式也定三个迭代器变量。

二、vector手动实现

类模版的声明和定义一般写在同一个文件下。创建一个vector.h(类实现过程)和test.cpp(接口测试),为了防止出现命名冲突,我外层套了一层命名空间zss,大家练习过程中可以不套。

下面是模拟原码实现的vector基础框架,成员就三个迭代器,vector本身是个顺序结构_start代表整个数组,_finish指向最后一个数据的下一个位置,_end_of_storage表示整个数组空间大小。

(1)构造

由于我们在声明的时候三个指针都给了缺省值nullptr,只要给缺省值了,用户不传参初始化列表会直接用缺省值初始化三个成员变量,所以我们提供的构造可以什么都没有。虽然什么都没有但是不能直接删掉,因为如果实现其他构造函数,要初始化三个指针还是得先走初始化列表。

(2)析构

到时候插入数据是需要自己手动new申请一段数组空间的,自己申请的空间析构也要对应匹配delete[ ]清理数据,并把指针置为空。

(3)尾插

push_back尾插之前必须确保有足够大的空间进行尾插数据,如果没有就进行空间的扩容,而要获取到数组空间大小用capacity()函数返回,顺便把size也求一下。由于扩容这一操作后面会经常使用所以封装成一个函数。

(4)扩容

reserve扩容采取深拷贝的思想,先开一段足够大小的tmp新空间,memcpy将旧空间数据一个字节一个字节拷给新空间,再将旧空间_start释放掉,把新空间tmp赋值给_start(reserve函数调用结束tmp自动销毁)最后更新一下_finish和_end_of_storage数据。

这里要注意的点是size()大小问题,大家看我还要再定个oldsize 变量存size()大小可能感到很奇怪,上面不是已经实现了size()函数,已经可以获取到数组大小了吗,怎么还多此一举存一下。

这里涉及到拷贝后_finish大小报错问题:

一开始我们计算的size()大小是还没替换空间前size的大小,替换空间后_start已经不再是原来空间而size却还是原来空间,两个不同空间的指针相加是会报错的,所以要用oldsize 变量存size()大小,这样_finish = _start + oldsize加的大小才是正确的。

(5)[ ]运算符重载

在数组的遍历中,最常用的就是[ ]、迭代器和范围for。内置类型下标遍历转换为解引用,自定义类型要实现下标遍历得重载运算法。有时候打印的数据具有常性不允许改变需要调用const版本,所以下面也实现了个const版本。

[ ]的实现:

5.1 迭代器的实现:

迭代器的实现也有分普通类型和const类型,const类型迭代器并不是在迭代器前面加个const就行,这是限制迭代器本身不能改变,而我们要的是数据不能改变,所以要typedef提供一个新的迭代器类型。

迭代器的行为是模拟指针并不代表它就是指针

使用:

范围for遍历:
范围for看起来很高大上,很便利,其实底层是依靠迭代器的实现,只要实现了迭代器范围for直接用。所以变层看似有[ ]、迭代器和范围for三种遍历方式,实际上只有[ ]和迭代器。

(6)尾删

pop_back尾删很简单,想象一下数组尾删是不是只要改变数组个数就好,同理vector尾删--_finish就行,到时候插入也是从_finish位置重新覆盖插入。

(7)插入

insert在任意位置插入一个元素,只要和插入有关都先考虑内存够不够问题,不够扩容。

如果是在中间插入是要把pos位置及之后数据向后移动一位再进行插入,移动利用迭代器更高效。

(8)删除

erase删除pos位置元素,同样利用迭代器将数据移动覆盖,最后--_finish个数。

要注意的是erase返回值还是一个迭代器,这是为了防止迭代器失效问题。

(9)迭代器失效

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。 对于vector可能会导致其迭代器失效的操作有:会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

9.1 reserve扩容迭代器失效

第一张图代码是一开始正常逻辑没修改过的代码,reserve内部会开新空间然后拷贝赋值销毁,通过调试看到,原本指向上面空间的_start指针指向了下面新开的空间,这些都很合理,但是insert在pos位置插入数据,这个pos还指向被销毁了的原空间,当下面while循环时it在新空间内向左移动找旧pos是永远找不到的。

遇到这种问题,要更新迭代器让pos指向新空间的原始位置,怎么计算就是:一开始要算出pos距离首元素大小,等扩容完了更新,请看第二张代码图。

9.2 insert后迭代器失效

VS默认insert后迭代器全部失效,这条没有解决办法,唯一的办法就是:别用!!!

9.3 erase迭代器失效

通过以下部分代码演示:删除数组中的所有偶数,在删除的过程中++it会使判断条件被跳过

图二:erase内部删除pos位置元素,后面元素会自动向前移一位,这时3覆盖2,但++it使3被跳过判断了。

图三:如果偶数在最后一个呢?_finish覆盖4,但又++it使结束条件直接跳过,野指针访问

图一                                    图二                                   图三

以上情况也是更新一下迭代器就行,erase有返回值只要重新接收返回值就OK

(10)resize初始化

resize的改变会影响数组的数据个数,数据不够就插入,太多就删数据,不够还空间不足就扩容+插入

第二个参数不是必须给的,当用户没给第二个参数时匿名对象初始化;int就是0,指针就nullptr

(11)普通拷贝构造

有两种写法v2(v1)和v2=v1,这两种都可以考虑复用写过的代码实现

(12)=运算符重载拷贝构造

现代写法:

一种很妙的写法,通过参数传递的浅拷贝思想和swap实现。v里面的数据等函数运行结束自动销毁,而我们想要的已经通过*this返回了。

三、其他构造方法

(一)initializer_list初始化

下面写法大家在刷题时是不是经常看到,这是C++11的一种构造方式,专门用于支持花括号 {} 初始化语法。它提供了一种统一的方式来初始化各种容器和自定义类型。

用法:

实现:复用reserve和push_back

(二)迭代器初始化

迭代器初始化引入了一个新概念,类模板的成员函数也可以是模板,这样不用固定整个类的迭代器,任意类型的迭代器都可以初始化了。

实现:还是复用了push_back,使整体代码更简洁

四、动态二维数组

vector<vector<int>>它本质上是一个二维动态数组,模版变量可以是任何类型的指针,当然也可以是vector<int>*指针类型。

想象 vector<vector<int>> 就像一组可以伸缩的抽屉柜:

外层 vector:这是一个大柜子,里面可以放很多抽屉(每个抽屉就是一个 vector<int>)

每个内层 vector:每个抽屉里可以放很多整数(int),而且每个抽屉的大小可以不一样

案例:力扣——杨辉三角

与静态数组对比的好处:

静态数组(如 int arr[3][4]):大小固定、每行长度必须相同、内存连续

vector<vector<int>>:大小可变、每行长度可以不同、内存不一定完全连续(外层连续,内层各自连续)

完整vector手动实现代码如下:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace zss
{template<class T>class vector{public:typedef T* iterator;//const 迭代器typedef const T* const_iterator;size_t capacity()const{return _end_of_storage - _start;}size_t size()const{return _finish - _start;}//迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}//1.构造//这玩意什么都不写也不能因为在声明给缺省值就删掉//因为自己实现了拷贝构造或者其他构造删掉就不是自动生成了//可能我们自己也会写其他构造initialize_list,允许用 { } 初始化对象vector(){}//13.initializer_list初始化vector(initializer_list<T> il){//走初始化列表把三个指针初始化了然后直接开空间尾插reserve(il.size());for (auto& e : il){push_back(e);}}//14.迭代器初始化//类模板的成函数模板,这样不用类的迭代器,任意迭代器都可以template<class InputIierator>vector(InputIierator first, InputIierator end){while (first != end){push_back(*first);++first;}}//2.析构~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}//3.尾插void push_back(const T& x){if (_finish == _end_of_storage){//扩容//没有容量就通过capacity()函数获取一个reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}//4.扩容void reserve(size_t n){//可能reserve被单独调用所以再判断一次容量if (n > capacity()){//这里原本_start、_finish、_end_of_storage都指向同一块空间//拷贝了_start指向新的空间,你用两个不同空间指针相减不可能得到size//所以更新前保存一下size()size_t oldsize = size();T* tmp = new T[n];//memcpy(tmp, _start, sizeof(T) * oldsize);//自定义类型string不能用memcpy,会指向同一块空间释放多次for (int i = 0; i < oldsize; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}}//5.[]T& operator[](size_t i){assert(i < size());return _start[i];}//6.const[]const T& operator[](size_t i)const{assert(i < size());return _start[i];}//7.尾删void pop_back(){assert(_finish > _start);--_finish;}//8.插入void  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;}//插入,因为是迭代器不存在没有小于0的情况iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = x;++_finish;}//9.删除元素iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}_finish--;return pos;}//10.迭代器失效处理//11.resizevoid resize(size_t n, const T& val = T()){if (n < size())_finish = _start + n;else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}//12.拷贝构造v2(v1)vector(const vector<T>& v){reserve(v.size());for (auto& e : v){push_back(e);}}//v2=v1//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		//如果有值先释放掉//		delete[] _start;//		_start = _finish = _end_of_storage = nullptr;//		//开新的空间//		reserve(v.size());//		//拷贝数据//		for (auto& e : v)//		{//			push_back(e);//		}//	}//	return *this;//}//现代写法void swap(vector<T>& v){std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._end_of_storage,_end_of_storage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}private://模拟STL库的实现iterator _start=nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;};
}

下次继续一起学习,感谢观看~

相关文章:

【C++】vector容器实现

目录 一、vector的成员变量 二、vector手动实现 &#xff08;1&#xff09;构造 &#xff08;2&#xff09;析构 &#xff08;3&#xff09;尾插 &#xff08;4&#xff09;扩容 &#xff08;5&#xff09;[ ]运算符重载 5.1 迭代器的实现&#xff1a; &#xff08;6&…...

RocketMQ 深度解析:消息中间件核心原理与实践指南

一、RocketMQ 概述 1.1 什么是 RocketMQ RocketMQ 是阿里巴巴开源的一款分布式消息中间件&#xff0c;后捐赠给 Apache 基金会成为顶级项目。它具有低延迟、高并发、高可用、高可靠等特点&#xff0c;广泛应用于订单交易、消息推送、流计算、IoT 等场景。 1.2 核心特性 高吞…...

使用Docker Compose部署Dify

目录 1. 克隆项目代码2. 准备配置文件3. 配置环境变量4. 启动服务5. 验证部署6. 访问服务注意事项 1. 克隆项目代码 首先&#xff0c;克隆Dify项目的1.4.0版本&#xff1a; git clone https://github.com/langgenius/dify.git --branch 1.4.02. 准备配置文件 进入docker目录…...

基于 Vue3 与 exceljs 实现自定义导出 Excel 模板

在开发中&#xff0c;我们需要常常为用户提供更多的数据录入方式&#xff0c;Excel 模板导出与导入是一个常见的功能点。本文将介绍如何使用 Vue3、exceljs 和 file-saver 实现一个自定义导出 Excel 模板&#xff0c;并在特定列添加下拉框选择的数据验证功能。 技术选型 excelj…...

杰发科技AC7840——CSE硬件加密模块使用(1)

1. 简介 2. 功能概述 3. 简单的代码分析 测试第二个代码例程 初始化随机数 这里的CSE_CMD_RND在FuncID中体现了 CSE_SECRET_KEY在17个用户KEY中体现 最后的读取RNG值&#xff0c;可以看出计算结果在PRAM中。 总的来看 和示例说明一样&#xff0c;CSE 初次使用&#xff0c;添加…...

前端地图数据格式标准及应用

前端地图数据格式标准及应用 坐标系EPSGgeojson标准格式基于OGC标准的地图服务shapefile文件3D模型数据常见地图框架 坐标系EPSG EPSG&#xff08;European Petroleum Survey Group&#xff09;是一个国际组织&#xff0c;负责维护和管理地理坐标系统和投影系统的标准化编码 E…...

threejs几何体BufferGeometry顶点

1. 几何体顶点位置数据和点模型 本章节主要目的是给大家讲解几何体geometry的顶点概念,相对偏底层一些&#xff0c;不过掌握以后&#xff0c;你更容易深入理解Threejs的几何体和模型对象。 缓冲类型几何体BufferGeometry threejs的长方体BoxGeometry、球体SphereGeometry等几…...

向量数据库选型实战指南:Milvus架构深度解析与技术对比

导读&#xff1a;随着大语言模型和AI应用的快速普及&#xff0c;传统数据库在处理高维向量数据时面临的性能瓶颈日益凸显。当文档经过嵌入模型处理生成768到1536维的向量后&#xff0c;传统B-Tree索引的检索效率会出现显著下降&#xff0c;而现代应用对毫秒级响应的严苛要求使得…...

java方法重写学习笔记

方法重写介绍 子类和父类有两个返回值&#xff0c;参数&#xff0c;名称都一样的方法&#xff0c; 子类的方法会覆盖父类的方法。 调用 public class Overide01 {public static void main(String[] args) {Dog dog new Dog();dog.cry();} }Animal类 public class Animal {…...

解决WPF短暂的白色闪烁(白色闪屏)

在 WPF 应用程序启动时出现 短暂的白色闪烁&#xff08;白色闪屏&#xff09;&#xff0c;通常是由于以下原因导致的&#xff1a; 主要原因 WPF 默认窗口背景是白色&#xff0c;在加载 UI 之前会短暂显示白色背景。 解决方案 设置窗口背景为透明或黑色&#xff08;推荐&…...

如何在Java中处理PDF文档(教程)

在开发文档管理系统、自动化工具或商业应用程序时&#xff0c;Java开发者常需处理PDF文档的编辑需求。无论是添加页面、调整内容尺寸、插入水印还是添加注释&#xff0c;选择一套可靠易用的Java PDF开发工具包至关重要。 JPedal&#xff08;Java PDF开发工具包&#xff09;的新…...

TensorBoard安装与基本操作指南(PyTorch)

文章目录 什么是TensorBoard&#xff1f;TensorBoardX与TensorBoard的依赖关系易混关系辨析Pytorch安装TensorBoard并验证1. TensorBoard安装和访问2. TensorBoard主要界面介绍实用技巧 什么是TensorBoard&#xff1f; TensorBoard是TensorFlow生态系统中的一款强大的可视化工…...

基于PyTorch的残差网络图像分类实现指南

以下是一份超过6000字的详细技术文档&#xff0c;介绍如何在Python环境下使用PyTorch框架实现ResNet进行图像分类任务&#xff0c;并部署在服务器环境运行。内容包含完整代码实现、原理分析和工程实践细节。 基于PyTorch的残差网络图像分类实现指南 目录 残差网络理论基础服务…...

2025/5/25 学习日记 linux进阶命令学习

tree:以树状结构显示目录下的文件和子目录&#xff0c;方便直观查看文件系统结构。 -d&#xff1a;仅显示目录&#xff0c;不显示文件。-L [层数]&#xff1a;限制显示的目录层级&#xff08;如 -L 2 表示显示当前目录下 2 层子目录&#xff09;。-h&#xff1a;以人类可读的格…...

【MPC控制 - 从ACC到自动驾驶】4 MPC的“实战演练”:ACC Simulink仿真与结果深度解读

【MPC控制 - 从ACC到自动驾驶】MPC的“实战演练”&#xff1a;ACC Simulink仿真与结果深度解读 在过去的几天里&#xff0c;我们一起&#xff1a; Day 1: 认识了ACC这位聪明的“跟车小能手”和MPC这位“深谋远虑的棋手”。Day 2: 给汽车“画了像”&#xff0c;建立了它的纵向…...

【时时三省】Python 语言----牛客网刷题笔记

目录 1,常用函数 1,input() 2,map() 3,split() 4,range() 5, 切片 6,列表推导式 山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 1,常用函数 1,input() 该函数遇到 换行停止接收,返回类型为字符串 2,map() 该函数出镜率较高,目的是将一个可迭…...

OPENEULER搭建私有云存储服务器

一、关闭防火墙和selinux 二、下载相关软件 下载nginx&#xff0c;mariadb、php、nextcloud 下载nextcloud&#xff1a; sudo wget https://download.nextcloud.com/server/releases/nextcloud-30.0.1.zip sudo unzip nextcloud-30.0.1.zip -d /var/www/html/ sudo chown -R…...

PyQt学习系列10-性能优化与调试技巧

PyQt学习系列笔记&#xff08;Python Qt框架&#xff09; 第十课&#xff1a;PyQt的性能优化与调试技巧 课程目标 掌握 PyQt应用的性能优化策略&#xff08;内存管理、渲染优化、多线程&#xff09;学习 调试技巧&#xff08;日志输出、断点设置、性能分析工具&#xff09;解…...

卷积神经网络(CNN)深度讲解

卷积神经网络&#xff08;CNN&#xff09; 本篇博客参考自大佬的开源书籍&#xff0c;帮助大家从头开始学习卷积神经网络&#xff0c;谢谢各位的支持了&#xff0c;在此期待各位能与我共同进步​ 卷积神经网络&#xff08;CNN&#xff09;是一种特殊的深度学习网络结构&#x…...

Docker部署Zookeeper集群

简介 ZooKeeper 是一个开源的分布式协调服务&#xff0c;由 Apache 软件基金会开发和维护。它主要用于管理和协调分布式系统中的多个节点&#xff0c;以解决分布式环境下的常见问题&#xff0c;如配置管理、服务发现、分布式锁等。ZooKeeper 提供了一种可靠的机制&#xff0c;…...

数据结构—(概述)

目录 一 数据结构&#xff0c;相关概念 1. 数据结构&#xff1a; 2. 数据(Data): 3. 数据元素(Data Element): 4. 数据项&#xff1a; 5. 数据对象(Data Object): 6. 容器&#xff08;container&#xff09;&#xff1a; 7. 结点&#xff08;Node&#xff09;&#xff…...

python打卡day34

GPU训练及类的call方法 知识点回归&#xff1a; CPU性能的查看&#xff1a;看架构代际、核心数、线程数GPU性能的查看&#xff1a;看显存、看级别、看架构代际GPU训练的方法&#xff1a;数据和模型移动到GPU device上类的call方法&#xff1a;为什么定义前向传播时可以直接写作…...

华为OD机试真题—— 流水线(2025B卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 B卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…...

【数据架构01】数据技术架构篇

✅ 9张高质量数据架构图&#xff1a;大数据平台功能架构、数据全生命周期管理图、AI技术融合架构等&#xff1b; &#x1f680;无论你是数据架构师、治理专家&#xff0c;还是数字化转型负责人&#xff0c;这份资料库都能为你提供体系化参考&#xff0c;高效解决“架构设计难、…...

【安全攻防与漏洞​】​​HTTPS中的常见攻击与防御​​

HTTPS 中常见攻击与防御策略涵盖中间人攻击&#xff08;MITM&#xff09;、SSL剥离、重放攻击等&#xff0c;帮助构建安全的 HTTPS 通信环境&#xff1a; 一、中间人攻击&#xff08;MITM&#xff09; 攻击原理 场景&#xff1a;攻击者通过伪造证书或劫持网络流量&#xff0c…...

esp32cmini SK6812 2个方式

1 #include <SPI.h> // ESP32-C系列的SPI引脚 #define MOSI_PIN 7 // ESP32-C3/C6的SPI MOSI引脚 #define NUM_LEDS 30 // LED灯带实际LED数量 - 确保与实际数量匹配&#xff01; #define SPI_CLOCK 10000000 // SPI时钟频率 // 颜色结构体 st…...

【数据集】30 m地表温度LST数据集

目录 数据概述🔧研究目标与意义🧠 算法核心组成1. 地表比辐射率(LSE)估算2. 大气校正(Atmospheric Correction)LST反演流程图📊 精度验证与评估结果参考《Generating the 30-m land surface temperature product over continental China and USA from Landsat 5/7/8 …...

【CATIA的二次开发07】草图编辑器对象结构及应用

【CATIA的二次开发07】草图编辑器对象结构及应用 草图编辑器(SketchEditor)是用于创建和编辑2D草图的核心对象。其对象结构遵循CATIA的层级关系,以下是详细说明及代码示例: 一、核心对象结构图 Application │ └─ Documents│└─ Document (.CATPart)│└─ Part│└─…...

IT | 词汇科普手册Ⅱ

目录 1.报文(Message) 2.Token(令牌) Token vs. Cookie Token vs. Key "碰一碰"支付 3.NFC 4.Nginx 5.JSON 6.前置机 前置机vs.Nginx反向代理 以PDA、WMS举例前置机场景 7.RabbitMQ 核心功能 1.报文(Message) 报文&#xff08;Message&#xff09;​​是系统或组件之…...

【 java 基础问题 第一篇 】

目录 1.概念 1.1.java的特定有哪些&#xff1f; 1.2.java有哪些优势哪些劣势&#xff1f; 1.3.java为什么可以跨平台&#xff1f; 1.4JVM,JDK,JRE它们有什么区别&#xff1f; 1.5.编译型语言与解释型语言的区别&#xff1f; 2.数据类型 2.1.long与int类型可以互转吗&…...