C++:模拟实现vector
目录
成员变量与迭代器
size
capacity
empty
迭代器有关函数
实现默认成员函数的前置准备
reserve
编辑
编辑
push_back
构造函数
无参构造
迭代器区间构造
n个val来进行构造
析构函数
拷贝构造函数
赋值重载
增删查改
clear
resize
pop_back
insert
erase
重载[]
print_Container
成员变量与迭代器
我们还是需要在一个命名空间里模拟实现vector,防止和标准库里的起冲突。
namespace zh
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}
解释说明:
1.vector是一个非常通用的容器,是一个动态大小的数组,可以存储任意类型的元素,并且能够自动调整大小以适应元素的添加和删除。所以我们的模拟实现要写成类模板。
2.vector可以看做顺序表的升级,但是模拟实现vector跟我们以往实现顺序表有所不同,顺序表是使用一个动态开辟的数组、数组有效元素个数size和数组容纳最大有效数据的个数capacity维护的,而模拟实现vector需要三个(模板参数)T* 类型的指针,而vector的迭代器功能恰恰又和T*类型指针类似,所以干脆把T*封装成迭代器。当然迭代器需要有两个版本,普通版本和const版本。
3.参数的含义
_start指向数组首元素,_finish指向最后一个有效元素的下一个位置, _end_of_storage指向数组空间末尾。
通过三个指针也可以模拟出size和capacity的功能。
size
返回有效数据个数的函数。
size_t size() const
{return _finish - _start;
}
capacity
返回数组最大容纳有效数据个数(容量大小)的函数。
size_t capacity() const
{return _end_of_storage - _start;
}
empty
判断数组是否为空,判断_start与_finish是否相等即可。
bool empty() const
{return _finish == _start;
}
迭代器有关函数
主要实现begin函数和end函数。
iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}
实现默认成员函数的前置准备
reserve
用于vector数组空间不足时扩容的函数(扩容成n个空间)。
void reserve(size_t n)
{if (n > capacity()) //n大于数组容量才扩容{size_t oldsize = size(); //用oldsize避免新_start和老_finish的问题T* tmp = new T[n];//memcpy(tmp, _start, size() * sizeof(T)); //这里是浅拷贝,如果是内置类型,没问题 //如果vector存的是自定义类型,就是大坑for (size_t i = 0; i < oldsize; ++i){tmp[i] = _start[i];}delete _start; //这里delete_start,_finish 和_end_of_storage是野指针//更新成员变量_start = tmp;_finish = tmp + oldsize; _end_of_storage = tmp + n;}
}
reserve有几个问题需要注意:
1.开空间的时候要使用new而不要用malloc,因为malloc只是去开空间,不会去调用构造函数。
2.新_start和_finish的问题。
错误示范。
将原有数据拷贝到新空间后,释放了旧空间的资源,_strat指向了新的空间,但是_finish和_end_of_storage还是指向旧空间,这两个指针就变成野指针了。而最关键的是_finish不能被正确赋值。
3.memcpy浅拷贝问题
memcpy(tmp, _start, size() * sizeof(T));
memcpy是浅拷贝,如果vector存的是内置类型,那么浅拷贝就没有问题,如果存的是自定义类型,那浅拷贝就是个大坑。假如vector存的是string类型,那么扩容时,将数据从旧空间拷贝到新空间时,因为是浅拷贝,所以两个空间里的string的_str是同一个地址,释放旧空间的时候就连带这把新空间的资源也释放了。
这样就扩容失败了,因为你把原空间的数据丢失了,而且搞不好有可能程序还会崩溃。
要解决这个问题,我们就得手动实现深拷贝, 因为new出来的空间如果是自定义类型的话就自动调用构造函数初始化了,所以这里走的是赋值重载来实现深拷贝。
push_back
用于在数组末尾尾插一个元素的函数。
void push_back(const T& x)
{//插入之前先判断空间是否足够if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//插入元素,更新_finish*_finish = x;_finish++;
}
构造函数
vector的构造函数我们实现无参构造、迭代器区间构造和n个val构造。
无参构造
无参构造其实我们并不需要写,因为已经在成员变声明时给了缺省值,编译器自动生成的无参构造函数走初始化列表满足需求了。但是由于我们写了其他构造函数,编译器就不自动生成了。
这里时候可以自己写无参构造,也可以用default强制编译器生成(C++11的用法)。
//构造
/*vector()
{}*///c++11 强制生成构造
vector() = default;
迭代器区间构造
//类模板的成员函数,还可以继续是函数模版
template<class InputIerator>
vector(InputIerator first, InputIerator last)
{while (first != last){push_back(*first); ++first;}
}
这里给这个函数再套一层模板是为了让vector不仅能用vector的迭代器区间构造,还能用其他容器(list、string等)的迭代器来进行构造。
这里又有个问题,就是while循环判断条件的!=不能改成<,因为<对于vector的迭代器时可以的,但是对于其他容器的迭代器,如list,last不一定比first要大。
n个val来进行构造
vector(size_t n, const T& val = T())
{//先开好空间reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}
使用的时候val可能不传参,所以要给缺省值。
因为val的类型不确定,可能是内置类型,也可能是自定义类型。
在不传参使用缺省值时
对于自定义类型,比如strng,先调用构造函数构造一个匿名对象,再拷贝构造给val。(编译器会优化,直接对val进行构造),这样val就有了缺省值。
对于内置类型,本来是没有构造函数的说法的,但是为了适应这里,也支持类似类那种使用构造函数初始化的方式。
int a = int();
int b = int(2);
int c(3);
cout << a << endl;
cout << b << endl;
cout << c << endl;
析构函数
直接delete就可以了,把三个迭代器置空。
//析构
~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
}
拷贝构造函数
先开好空间,然后尾插就可以了。
//拷贝构造
vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v){push_back(e);}
}
赋值重载
首先实现一个交换函数,然后传值调用,将两个对象交换即可。
//void swap(vector& v) 可以这样写
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<T>& operator=(vector<T> v)
{swap(v);return *this;
}
增删查改
clear
不需要真的删除,直接将更改_finish的值即可。
void clear()
{_finish = _start;
}
resize
控制有效数据个数。
- 若n < size,直接将_finish更改为_start + n即可。
- 若_size < n < capacity或者n > capacity,直接扩容成n个空间(空间足够就不会扩容),从_finish拷贝足够数量的val即可。
void resize(size_t n, T val = T())
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n) {*_finish = val;++_finish;}}
}
pop_back
先判断数组是否为空,尾删一个元素,_finish-- 即可。
void pop_back()
{//判断下数组是否为空assert(!empty());--_finish;
}
insert
在pos位置插入一个元素。
iterator insert(iterator pos, const T& x) //pos不会为0,因为是有效的迭代器
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage) //涉及到扩容,pos会失效,pos指向原来的空间{size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入元素,更新*pos = x;++_finish;return pos;
}
注意的问题:
1.如果插入涉及到了扩容,要提前把pos相对于首元素的相对长度记录下来,扩容完毕后,更新pos。因为扩容会导致pos失效。
2.插入之后要返回新元素的迭代器。(这里其实也算迭代器是失效了,因为pos指向的元素发生了更改,迭代器失效了就不要在使用了。)
erase
删除pos位置的元素,删除完后返回删除元素下一位置的迭代器。
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;
}
抛出一个问题,利用迭代器删除vector中所有的偶数。
错误做法
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}it++;
}
删完一个偶数后,it已经是下一元素的迭代器了,it不需要++了。
正确做法
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}else{++it;}
}
重载[]
为了方便访问和修改数组中的元素。
T& operator[](size_t i)
{assert(i < size());return _start[i];
}const T& operator[](size_t i) const
{assert(i < size());return _start[i];
}
print_Container
通用打印容器函数,套一层模板即可。
注意:
template<class Container>
void print_Container(const Container& v)
{//typename vector<T>::const_iterator it = v.begin(); //typename标定为类型 //从没有实例化的类模板取出来的可能是类型或者成员变量,编译器无法区分auto it = v.begin(); while (it != v.end()){cout << *it << ' ';++it;}cout << endl;/*for (auto num : v){cout << num << ' ';}cout << endl;*/
}
从未实例化的类取出来的有可能是类型或者成员变量,要加关键字typename告诉编译器是类型,不加的话会发生编译错误。
当然直接用auto更方便。
拜拜,下期再见😏
摸鱼ing😴✨🎞
相关文章:
C++:模拟实现vector
目录 成员变量与迭代器 size capacity empty 迭代器有关函数 实现默认成员函数的前置准备 reserve 编辑 编辑 push_back 构造函数 无参构造 迭代器区间构造 n个val来进行构造 析构函数 拷贝构造函数 赋值重载 增删查改 clear resize pop_back inser…...
Leecode SQL 184. Department Highest Salary 找出tie
Department Highest Salary 注意!要找出 tie 的 highest salary! Write a solution to find employees who have the highest salary in each of the departments. Return the result table in any order. The result format is in the following ex…...
[Redis][典型运用][缓存]详细讲解
目录 0.什么是缓存?1.使用Redis作为缓存1.为什么用?2.如何用? 2.缓存的更新策略0.前言1.定期生成2.实时生成 3.缓存相关问题1.缓存预热(Cache Preheating)2.缓存穿透(Cache Penetration)3.缓存雪崩(Cache Avalanche)4.缓存击穿(Cache Breakdo…...
GPG error golang 1.19
1. 问题描述及原因分析 在飞腾2000的服务器,OS为Kylin Linux Advanced Server release V10环境下,docker版本为18.09.0(docker-engine-18.09.0-101.ky10.aarch64),基于容器镜像golang:1.19编译新的容器镜像࿰…...
Linux如何查看每个文件及文件夹的大小
查看当前目录下每个文件夹的大小,包括其内部所有文件: du -sh *-s:仅显示每个文件夹的总大小,而不是每个文件。-h:以人类可读的格式显示。...
Word样式的同步与重置
有时候我们需要修改Word中的样式,实现排版的个性化。 如何同步样式到其他电脑上? Word中的样式是由Normal.dotm文件控制的,对样式所有的设置和修改,都会保存到这个问题件中,所以我们只需要在设置好样式以后ÿ…...
力扣 —— 跳跃游戏
题目一(中等) 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 示例 1&…...
SOCKS5代理和HTTP代理哪个快?深度解析两者的速度差异
在现代互联网环境中,使用代理IP已经成为了许多人日常生活和工作的必备工具。无论是为了保护隐私,还是为了访问某些特定资源,代理IP都扮演着重要的角色。今天,我们就来聊聊SOCKS5代理和HTTP代理,看看这两者到底哪个更快…...
工具介绍---效率高+实用
Visual Studio Code (VS Code) 功能特点: 智能代码提示:内置的智能代码提示功能可以自动完成函数、变量等的输入,提高代码编写速度。插件丰富:支持成千上万的扩展插件,例如代码片段、主题、Linting等,能够…...
本地部署开源在线PPT制作与演示应用PPTist并实现异地远程使用
文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统环境本地部署开源在线演示文稿应用PPTist,并结合cpolar内网穿透工具实现随时随地远程访问与使用该项目。 PPTist …...
leetcode_238:除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂…...
网络协议详解--IPv6
IPv6产生背景 (1)地址空间的耗尽:因特网呈指数级发展,导致IPv4地址空间几乎耗尽。虽然采用了子网划分、CIDR和NAT地址转换技术,但这没有从根源解决地址耗尽的问题 (2)IP层安全需求的增长&#x…...
阿里云域名注册购买和备案
文章目录 1、阿里云首页搜索 域名注册2、点击 控制台3、域名控制台 1、阿里云首页搜索 域名注册 2、点击 控制台 3、域名控制台...
【经典机器学习算法】谱聚类算法及其实现(python)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀深度学习_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. 前…...
【Linux】Linux环境基础开发工具使用
Linux开发工具 Linux编辑器-vim使用 1. vim的基本概念 vim的三种模式,分别是命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode)。 正常/普通/命令模式: …...
Halcon基础系列1-基础算子
1 窗口介绍 打开Halcon 的主界面主要有图形窗口、算子窗口、变量窗口和程序窗口,可拖动调整位置,关闭后可在窗口下拉选项中找到。 2 显示操作 关闭-dev_close_window() 打开-dev_open_window (0, 0, 712, 512, black, WindowHandle) 显示-dev_display(…...
【AI大模型】深入Transformer架构:编码器部分的实现与解析(上)
目录 🍔 编码器介绍 🍔 掩码张量 2.1 掩码张量介绍 2.2 掩码张量的作用 2.3 生成掩码张量的代码分析 2.4 掩码张量的可视化 2.5 掩码张量总结 🍔 注意力机制 3.1 注意力计算规则的代码分析 3.2 带有mask的输入参数: 3.…...
spring学习日记-day7-整合mybatis
一、学习目标 spring整合MyBatis的原理主要涉及到将MyBatis的Mapper映射文件交由Spring容器管理,并将其注入到MyBatis的SqlSessionFactory中,从而实现两者的整合。 二、整合mybatis 1.写一个mybatis测试案例 项目结构: 1.数据库 CREATE DA…...
【YOLO目标检测行人与车数据集】共5607张、已标注txt格式、有训练好的yolov5的模型
目录 说明图片示例 说明 数据集格式:YOLO格式 图片数量:5607 标注数量(txt文件个数):5607 标注类别数:2 标注类别名称:person、car 数据集下载:行人与车数据集 图片示例 数据集图片: …...
JMeter中线程组、HTTP请求的常见参数解释
在JMeter中,线程组和HTTP请求是进行性能测试的两个核心组件。以下是它们的一些常见相关参数的解释: 线程组参数 线程数 指定模拟的用户数,即并发执行的线程数。 Ramp-Up时间(秒) 指定所有线程启动的时间间隔。在这…...
优化Mysql
目录 Mysql优化就四种:定位慢查询/sql执行计划/索引/Sql优化经验... 2 1Mysql如何定位慢查询?... 2 2Sql语句执行很慢,如何分析呢?... 3 2.1那这个SQL语句执行很慢,如何分析呢?. 3 3.了解过索引吗?(什么是索引)…...
如何使用MethodChannel通信
文章目录 1 概念介绍2 实现方法3 经验总结我们在上一章回中介绍了Visibility组件相关的内容,本章回中将介绍Flutter与原生平台通信相关的内容.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在移动开发领域以Android和IOS SDK开发出的应用程序叫原生开发,开发同一个程序…...
【JavaWeb】JavaWeb笔记 HTTP
文章目录 简介HTTP1.0和HTTP1.1的区别 请求和响应报文报文的格式请求报文form表单发送GET请求特点GET请求行,请求头,请求体form表单发送post请求特点post的请求行 请求头 请求体 响应报文响应状态码更多的响应状态码 简介 HTTP 超文本传输协议 (HTTP-Hyper Text transfer proto…...
Java项目实战II基于Java+Spring Boot+MySQL的甘肃非物质文化网站设计与实现(源码+数据库+文档)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者 一、前言 甘肃省作为中国历史文化名省,拥有丰富的非物质文化遗产资源,涵盖表演艺术、手…...
数据结构--包装类简单认识泛型
目录 1 包装类 1.1 基本数据类型和对应的包装类 1.2 装箱和拆箱,自动装箱和自动拆箱 2 什么是泛型 3 引出泛型 3.1 语法 4 泛型类的使用 4.1 语法 4.2 示例 5 泛型的上界 5.1 语法 5.2 示例 5.3 复杂示例 8 泛型方法 8.1 定义语法 8.2 示例 总结 1 …...
c#使用winscp库实现FTP/SFTP/SCP的获取列表、上传和下载功能
网上写c#调用winscp实现的资料很少,且写的不够详细。本人查了下winscp的libraries说明,写了个小工具,供大家参考。 winscp的接口说明地址如下: WinSCP .NET Assembly and COM Library :: WinSCP 一、先展示一下小工具的界面 1、…...
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...
达梦数据库开启归档模式
目录 一、什么是归档模式? 二、开启归档模式的步骤 1、创建归档目录 2、进入dm数据库bin目录 3、登录数据库 4、关闭数据库 5、启动数据库到Mount状态 6、增加本地归档日志文件 7、开启归档 8、启动数据库 9、验证是否开启成功 三、开启归档模式的优…...
C++ 语言特性07 - 静态成员的初始化
一:概述 1. 静态成员变量通常在类定义内部声明,并在类定义外部定义和初始化。 class MyClass { public:static int staticVar; // 声明 };int MyClass::staticVar 42; // 定义和初始化 2. 从C11开始,可以在类内直接初始化静态数据成员&am…...
【数据结构】图论基础
文章目录 图的概念图的基本概念图的类型图的表示方法 图的相关基本概念1. 路径(Path)2. 连通性(Connectivity)3. 图的度(Degree)4. 子图(Subgraph)5. 生成树(Spanning Tr…...
导航网站怎么做seo/奉化网站关键词优化费用
题目链接 没早发现这个DP,一直纠结各种图论题,蛋疼。。无奈水平太菜,最后两个小时都没搞出,本来想开10个标记数组的。。。。搞到最后半小时,发现同颜色的不会算,暴力吧。。。然后有个小细节没搞好ÿ…...
app网站建设 - 百度/站内搜索工具
首先找根网线把路由器连接到ADSL猫的WAN口上,然后再用根网线把路由器连接到电脑上 ,确保连接成功后,打开你电脑的IE浏览器,然后输入你路由器面的ip地址,一般情况都是192.168.1.1,然后回车会弹出输入用户名和…...
一起做网店 网站打不开/网站注册搜索引擎的目的是
这个是按照event时间的个数收集的; 嗯,按照回滚的时间1S转载于:https://www.cnblogs.com/hzchh/p/8109980.html...
wordpress post fonts/营销网站建设服务
http://cocobear.info/blog/2009/01/16/use-python-deal-with-excel/ 使用Python处理Excel表格 2009年01月16日 给俺的boss写的一个小工具,使用Python对Excel进行统计,然后把结束生成一个新的Excel表格,使用到了xlrd和pyExcelerator两个库。 …...
重庆做企业网站/杭州网络排名优化
有时候我们在桌面上新建一个文件夹,大家都知道新建文件夹的位置是系统根据桌面上最后一个默认排序的,想把这个文件夹放在自己容易辨识的地方,可能是右下角,可能是右上角,那这个拖拽是怎么实现的呢? 一、我们…...
调用wordpress数据/网络推广的优势
核心提示:WMS是仓库管理系统(WarehouseManagement System) 的缩写,仓库管理系统是通过入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能,实现完善的企业仓储信息管理。现代医药物流WMS功能需求又有什么特殊之处呢?WMS一般…...