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…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...