vector类模拟实现(c++)(学习笔记)
vector
- 构造函数
- 析构函数
- []
- push_back
- size()
- capacity()
- reserve()
- push_back()
- 迭代器实现
- 非const和const版本
- pop_back()
- resize()
- insert()***重点
- erase()***重点
- 再谈构造函数!
- 拷贝构造函数****(重点)
- =运算符重载***(重点)
基本框架:
namespace xty
{template<class T>class vector {public:typedef T* iterator;/////...///private:iterator _strat; //起始位置iterator _finish; //最后一个元素的下一个地址iterator _end_of_storage; //容量的最后一个元素};
}
vector的大致形状如下:黄色代表每天满的地方。

构造函数
使用初始化列表实现一个简单的无参构造函数。
//无参构造函数vector():_finish(nullptr),_start(nullptr),_end_of_storage(nullptr){}
析构函数
记住要带[]即可。
~vector(){delete[] _start; //用带括号的_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}
[]
T& operator[](size_t pos){assert(pos < size());return *(_start + pos);}const T& operator[](size_t pos) const{assert(pos < size());return *(_start + pos);}
push_back
size()
size_t size() const{return _finish - _start;}
capacity()
size_t capacity() const {return _end_of_storage - _start;}
reserve()
因为push_back涉及到扩容函数,需要实现reserve()。
如下示例:
void reserve(size_t n){if (n > capacity()){T* tem = new T[n];if (_start){memcpy(tem, _start, sizeof(T)*size()); //拷贝过去delete[] _start;}_start = tem;_finish = _start + size(); //error_end_of_storage = _start + n;}}
问题1:_finish赋值出错,出bug了,是因为size()函数,调用了空指针,导致报错。
改正:
因为delete之后,原数据就被清空了,所以可以提前保存一下size()的大小。
void reserve(size_t n){if (n > capacity()){T* tem = new T[n];const size_t sz = size(); //提前保存szif (_start){memcpy(tem, _start, sizeof(T)*size()); //拷贝过去delete[] _start;}_start = tem;_finish = _start + sz; //使用sz赋值_end_of_storage = _start + n;}}
push_back()
逻辑比较简单,在vector的尾部添加一个val,就需要一些前置函数。
//尾插void push_back(const T& val){//满了扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}//插入数据*_finish = val;_finish++;}
迭代器实现
该逻辑也比较简单,注意实现const的版本。
非const和const版本
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;}
pop_back()
尾删。
bool empty(){return _start == _finish;}//尾删void pop_back(){assert(!empty());_finish--;}
resize()
和string逻辑差不多。
void resize(size_t n, const T& val = T()){//一样大,直接返回if (n == size()) {return;}if (n<size()) //小于直接修改_finish{while (n != size()){--_finish; }}else{if (n > capacity()) //大于容量先扩容{reserve(n);}while (n != size()){push_back(val);}}}
优化:该函数多次调用push_back()使用while,效率低。
void resize(size_t n, const T& val = T()){if (n == size()){return;}if (n < size()){ _finish = _start + n; //直接移动_finish}else{if (n > capacity()){reserve(n);}while (_finish != _start + n) //使用指针操作,减少调用{*_finish = val;_finish++;}}}
insert()***重点
传入迭代器的位置,插入一个元素。
void insert(iterator pos ,const T& val){//检测pos位置是否合法assert(pos >= _start);assert(pos <= _finish);// 满了需要扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}//从后往前移动iterator end = _finish;while (end > pos){*end = *(end - 1);end--;}*pos = val; //在该位置赋值_finish++;}
算法问题1:会造成迭代器失效,迭代器失效,实际就是迭代器底层对应指针所指[空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器程序可能会崩溃)。
该程序正常来说没有问题,当出现扩容的时候,reserve()会删除原来的空间,去申请新的空间,因此会导致pos指向的那段空间被释放掉,pos变成野指针。
改正:记录插入的相对位置,扩容后根据相对位置更新pos的值。
void insert(iterator pos, const T& val){//检测pos位置是否合法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; //扩容后重新给pos值}//从后往前移动iterator end = _finish;while (end > pos){*end = *(end - 1);end--;}*pos = val; //在该位置赋值_finish++;}
算法问题2:执行insert后,如果扩容,pos位置已经改变了,而函数外面的pos因为是值传递,并没有修改,同样导致了野指针的问题。(迭代器再一次失效!)
解决办法:
- pos传引用可以吗?
不可以。如下图:如果传入v.begin(),会报错。因为begin()是传值返回,传值返回有一个临时变量,而临时变量具有常性,不能被修改,insert里面就不能修改了!

- 通过返回值解决可以吗?
可以,我们可以利用insert返回值的特性,来更新pos防止失效!
如下图:这样就解决问题了。
最终版本:
iterator insert(iterator pos, const T& val){//检测pos位置是否合法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; //扩容后重新给pos值}//从后往前移动iterator end = _finish;while (end > pos){*end = *(end - 1);end--;}*pos = val; //在该位置赋值_finish++;return pos;}
总结:使用insert后,我们默认迭代器失效!因为我们不知道何时执行扩容操作,因此需要重新对pos赋值,防止这类情况发生!
erase()***重点
指定位置执行删除操作。
void erase(iterator pos){assert(pos >= _start);assert(pos < _finish);auto end = pos + 1;while (end < _finish){//后给前,从前往后*(end - 1) = *end;end++;}_finish--;}
问题1:erase会导致迭代器失效?
==会导致!==如果删除最后一个位置,最后一个位置就变成了空位置,导致pos也指向了不该指向的位置。因此,erase()执行过后,应该重新给pos赋值再使用!
最终版本:添加返回值。
iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);auto end = pos + 1;while (end < _finish){//后给前,从前往后*(end - 1) = *end;end++;}_finish--;return pos;}
最后给大家一个例子自己感受:
该例子在VS2019会报错
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0;
}
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}return 0;
}
再谈构造函数!
这次实现一个可以规定数量和内容的构造函数。
//正常实现vector(size_t n, const T& val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);size_t len = n;while (n--){push_back(val);}}//构造函数由迭代器实现template<class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (first != last){push_back(*first);++first;}}
当我们满心欢喜的实现好这两个构造函数后,想要测试一下。结果报错了。
输入: vector vx(10,5);

这是为什么呢?因为10,5都被编译器认为是int类型,而编译器在函数重载时,会自动调用最合适的,而它认为第二个函数更适合自己(),因此解引用的时候产生非法间接寻址!
因此我们需要再次实现一个int型的函数重载!
vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);size_t len = n;while (len--){push_back(val);}}
迭代器构造还支持以下方法:
int a[] = { 1, 2, 3 };vector<int> v4(a, a + 3);for (auto e : v4){std::cout << e << " ";}std::cout << std::endl;
}
拷贝构造函数****(重点)
如果我们不自己实现拷贝构造函数,编译器就会默认生成一个,但是编译器默认生成的是浅拷贝,不可以。
//拷贝构造vector(const vector<T>& v){//扩容reserve(v.capacity());memcpy(_start, v._start, sizeof(T) * v.size());_finish = _start + v.size();}
现在我们写完这个函数的拷贝构造之后,看看是否有问题:

提前告诉大家,这个程序会崩溃,因为memcpy()实现的是浅拷贝,他仅仅会拷贝v3的首尾指针,并不会开一个新的空间去存储相应的字符串。所以,程序结束时,调用析构函数,会连续析构两次!
**解决办法:**不适用memcpy()自己实现深拷贝,使用‘=’即可实现,因为string赋值操作就是深拷贝,string的赋值,就会先开空间,再拷贝!

//拷贝构造vector(const vector<T>& v){//扩容reserve(v.capacity());//memcpy(_start, v._start, sizeof(T) * v.size());for (size_t i = 0; i <v.size(); i++){_start[i] = v._start[i]; //变成string对象的拷贝}_finish = _start + v.size();}
但是reverse()也会产生这个浅拷贝的问题,因此将reserve也应该改成深拷贝。
void reserve(size_t n){if (n > capacity()){T* tem = new T[n];const size_t sz = size(); //提前保存szif (_start){//memcpy(tem, _start, sizeof(T)*size()); //拷贝过去for (size_t i = 0; i < size(); i++){tem[i] = _start[i];}delete[] _start;}_start = tem;_finish = _start + sz; //使用sz赋值_end_of_storage = _start + n;}}
这样vector的问题就解决了。但是vector<vector<int>>还有问题!!!请看赋值重载的部分。
=运算符重载***(重点)
这里暴露了一个问题,就是虽然外面的vector是深拷贝,但是里面的vector是浅拷贝,是由于没有写vector的赋值重载,再补充一个赋值重载即可!
vector<T>& operator=(vector<T> v){swap(v);return *this;}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}

运算符重载和拷贝构造这里写的不太详细,后续还会补充!
相关文章:
vector类模拟实现(c++)(学习笔记)
vector 构造函数析构函数[]push_backsize()capacity()reserve()push_back() 迭代器实现非const和const版本 pop_back()resize()insert()***重点erase()***重点再谈构造函数!拷贝构造函数****(重点)运算符重载***(重点)…...
Redis Sentinel 哨兵模式
Sentinel 哨兵模式 Redis Sentinel 官网 Redis 的 Sentinel 文档 -- Redis中国用户组(CRUG) Sentinel Redis 命令参考(红色) Sentinel 通过监控的方式获取主机的工作状态是否正常,当主机发生故障时, Senti…...
实用篇-MQ消息队列
一、初识MQ 通讯分为同步通讯和异步通讯,同步通讯就比如我们日常生活中的打电话,看直播,能够得到及时的反馈。而异步通讯则类似于聊天软件聊天,不需要建立实时的连接,并且可以进行建立多个业务一起异步执行 1. 同步通…...
springboot打包时依赖jar和项目jar分开打包;jar包瘦身
概述 最近感觉项目在部署时时jar包传输太慢了; 看了下jar包内容,除了项目代码,其余大部分都是依赖jar; 平时改动较多的只是项目代码,依赖jar改动比较少; 所以就在想能不能分开打包;这样只部署项…...
嵌入式系统的元素
注意:关于嵌入式系统的元素这一块儿内容,定义太多了。例如:吉姆莱丁 著,陈会翔 译,由清华大学出版社出版的《构建高性能嵌入式系统》中提到:嵌入式系统通常由电源、时基、数字处理、内存、软件和固件、专用…...
提升ChatGPT答案质量和准确性的方法Prompt engineering实用的prompt灵感和技巧
文章目录 1. 实用的prompt灵感和技巧小技巧常用prompt保存到输入法中普通promptprompt通用公式保存到输入法快捷指令中尝试用英语去写prompt沉浸式翻译软件3. 补充1. 实用的prompt灵感和技巧 解释***,并且给出暗喻/隐喻/类比(解释术语、专业名称,用一个词或短语指出常见的一…...
[Machine Learning] Learning with Noisy Labels
文章目录 随机分类噪声 (Random Classification Noise, RCN)类别依赖的标签噪声 (Class-Dependent Noise, CCN)二分类多分类 实例和类别依赖的标签噪声 (Instance and Label-Dependent Noise, ILN) 标签噪声是指分类任务中的标签被错误地标记。这可能是由于各种原因,…...
集简云slack(自建)无需API开发轻松连接OA、电商、营销、CRM、用户运营、推广、客服等近千款系统
slack是一个工作效率管理平台,让每个人都能够使用无代码自动化和 AI 功能,还可以无缝连接搜索和知识共享,并确保团队保持联系和参与。在世界各地,Slack 不仅受到公司的信任,同时也是人们偏好使用的平台。 官网&#x…...
Idea 对容器中的 Java 程序断点远程调试
第一种:简单粗暴型 直接在java程序中添加log.info(),根据需要打印信息然后打包覆盖,根据日志查看相关信息 第二种:远程调试 在IDEA右上角点击编辑配置设置相关参数在Dockerfile中加入 "-jar", "-agentlib:jdwp…...
vscode设置保存后,自动格式化代码
第一步:打开setting.json文件 第二步:在setting.json中加入以下代码 "editor.formatOnType": true, "editor.formatOnSave": true, "editor.formatOnPaste": true...
datagrip出现 java.net.ConnectException: Connection refused: connect.
出现这样的情况要看一下hadoop有没有启动 start-all.sh nohup /export/server/apache-hive-3.1.2-bin/bin/hive --service hiveserver2 & scp -r /export/server/apache-hive-3.1.2-bin/ node3:/export/server/ /export/server/apache-hive-3.1.2-bin/bin/hive show databa…...
Docker 安装ELK7.7.1
(在安装之前,本方法必须安装jdk1.8以上版本) 一、安装elasticsearch 1、下载elasticsearch7镜像:docker pull elasticsearch:7.7.1 2、创建挂载目录:mkdir -p /data/elk/es/{config,data,logs} 3、赋予权限:chown -R 1000:100…...
决策树算法
决策树算法是一种用于分类和回归问题的机器学习算法。它通过构建树形结构来进行决策,每个内部节点代表一个特征或属性,每个叶子节点代表一个类别或值。 下面是决策树算法的一般步骤: 数据准备:收集相关的训练数据,并对…...
maven之pom文件详解
一、maven官网 maven官网 maven官网pom文件详解链接 二、maven之pom 1、maven项目的目录结构 pom文件定于了一个maven项目的maven配置,一般pom文件的放在项目或者模块的根目录下。 maven的遵循约定大于配置,约定了如下的目录结构: 目录目…...
深度学习之基于Python+OpenCV+dlib的考生信息人脸识别系统(GUI界面)
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习在人脸识别领域的应用已经取得了显著的进展。Python是一种常用的编程语言,它提供了许多强大的库…...
创建javaEE项目(无maven),JSP(九大内置对象)、Servlet(生命周期)了解
一、Servlet和jsp 0.创建web项目(无maven): 1.创建一个普通的java项目 2.项目根目录右键,添加模板 3.配置tomcat服务器 4.配置项目tomcat依赖 1.Servlet(Server Applet)服务端小程序 用户通过浏览器发送一个请求,服务器tomcat接收到后&…...
BIOS开发笔记 - HDA Audio
在PC中,音频输出是一个重要的功能之一,目前大多数采用的是英特尔高清晰音效(英语:Intel High Definition Audio,简称为HD Audio或IHD)方案,它是由Intel于2004年所提出的音效技术,能够展现高清晰度的音质效果,且能进行多声道的播放,在音质(音效质量)上超越过去的其他…...
C语言——选择排序
完整代码: //选择排序 // 选择排序是一种简单直观的排序算法。它的工作原理如下:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大&am…...
vue详细安装教程
这里写目录标题 一、下载和安装node二、创建全局安装目录和缓存日志目录三、安装vue四、创建一个应用程序五、3x版本创建六、创建一个案例 一、下载和安装node 官网下载地址:https://nodejs.org/en/download 选择适合自己的版本,推荐LTS,长久…...
Java 正则表达式字符篇
精确匹配一个字符 精确匹配字符串 abc , //精确匹配字符串 "abc"String regexabc "abc";System.out.println("abc".matches(regexabc));// trueSystem.out.println("ABC".matches(regexabc));// falseSystem.out.println…...
AI小白必看:收藏这份从零入门大模型的核心概念指南
本文通过一个生动的故事,用通俗易懂的方式讲解了AI领域最核心的7个概念:LLM(大语言模型)、Agent(智能体)、Skill(技能包)、MCP(模型上下文协议)、IDE…...
掌握大模型Function Call能力:小白程序员必学训练秘籍(收藏版)
大模型的Function Call能力并非与生俱来,而是通过两个关键训练阶段——SFT和RLHF——精心培养的。SFT通过大量包含工具调用样本的监督微调,让模型学会如何输出结构化JSON调用请求;而RLHF则通过人类反馈强化学习,教会模型何时该调用…...
【实战指南】Ubuntu SSH服务配置与XShell/Xftp高效连接全解析
1. 为什么需要SSH远程连接Ubuntu? 作为开发者或运维人员,我们经常需要管理远程服务器。想象一下,你正在咖啡馆用Windows笔记本,突然需要紧急修改线上Ubuntu服务器的配置——这时候SSH就是你的救命稻草。它就像一把安全钥匙&#x…...
Python调用MATLAB引擎避坑指南:从安装路径选择到`setup.py` install命令的完整实战
Python调用MATLAB引擎避坑指南:从安装路径选择到setup.py install命令的完整实战 在科学计算和工程仿真领域,MATLAB和Python各有优势。许多开发者希望将两者结合使用,但安装MATLAB引擎到Python环境时常常遇到各种"玄学"问题。本文将…...
如何准备打动评审的物联网与硬件创业技术演讲
1. 从听众到讲者:在EE Live分享你的硬件与物联网洞见如果你是一名电子设计工程师、嵌入式开发者,或者正在硬件创业的浪潮中摸索,那么EE Live这个名字对你来说应该不陌生。这个由EE Times主办的年度盛会,前身是DESIGN West…...
3步解锁SWF逆向工程:JPEXS开源工具深度解析
3步解锁SWF逆向工程:JPEXS开源工具深度解析 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler 你是否曾面对一个陈旧的SWF文件束手无策?当Flash技术逐渐退出历史舞台…...
引用格式错乱导致学术不端?Perplexity官方未公开的4种强制校准法,仅限内部研究员使用!
更多请点击: https://intelliparadigm.com 第一章:Perplexity引用格式设置教程 Perplexity 本身不提供原生的参考文献管理功能,但其生成的回答可导出为 Markdown 或纯文本,便于后续在学术写作中按标准格式(如 APA、ML…...
FastDFS整合Nginx踩坑记:升级1.22.0修复CVE-2021-23017,如何平滑保留模块不报错?
FastDFS整合Nginx安全升级实战:从漏洞修复到模块兼容的全流程指南 最近在维护一个使用FastDFS作为分布式存储的生产环境时,遇到了Nginx的CVE-2021-23017安全漏洞问题。这个漏洞可能允许攻击者通过特制的DNS响应导致工作进程崩溃,对于线上业务…...
HiveWE:现代魔兽争霸III地图编辑器终极指南
HiveWE:现代魔兽争霸III地图编辑器终极指南 【免费下载链接】HiveWE A Warcraft III world editor. 项目地址: https://gitcode.com/gh_mirrors/hi/HiveWE 还在为魔兽争霸III原版地图编辑器的缓慢加载和复杂操作而烦恼吗?HiveWE作为一款专注于速度…...
SKILLS All-in-one:开源AI Agent技能库,标准化Prompt与工具函数,提升开发效率
1. 项目定位与核心价值如果你和我一样,在过去一年里深度使用过 Claude Code、ChatGPT 或者尝试搭建自己的 AI Agent 工作流,那你一定遇到过这个痛点:每次想给 AI 装个新“技能”,都得自己从头写 Prompt、设计工具调用逻辑、处理错…...
