C++-vector模拟实现
###vector底层相当于是数组,查看源码可以发现,这个类的私有成员变量是三个迭代器;在实现时迭代器就可以当作是vector里面的元素的指针类型;
###vector是一个类模板,实现时也应当按照这样的写法用一个模板去实现,类模板vector中数据类型是T;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
_start指向vector的开始地址,_finish指向vector的有效元素的结束地址,_end_of_storage指向vector最大存储数据的地址;
一、迭代器
typedef T* iterator;
typedef const T* const_iterator;//迭代器
iterator begin()
{return _start;
}
const_iterator begin()const
{return _start;
}
iterator end()
{return _finish;
}
const_iterator end()const
{return _finish;
}
二、容量
size_t size()const{return _finish - _start;}size_t capacity()const{return _end_of_storage - _start;}bool empty()const{return size() == 0;}void reserve(size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//memcpy(tmp, _start, size() * sizeof(T));//浅拷贝for (size_t i = 0; i < old_size; i++)//深拷贝{tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}}void resize(size_t n, const T& val = T())//匿名对象作为缺省值{if (n < capacity()){_finish = _start + n;}else{reserve(n);for (iterator i = _finish ; i < _start + n; i++){Insert(i,val);}}}
1、reserve
基本思路:开辟一个临时的数组存放T类型的数据,让这个数组的大小为指定的n,将vector对象中的数据全部放到这个临时数组中,再清空vector中的数据,最后让vector对象存放数据的地址就是这个临时数组的地址;
两个关键点:
- old_size:若是不先记录下size的话,后面给_finish重新赋值时使用到size(),size()函数中_size不再是原来的_size,而是tmp,此时用_size去减_finish会出错;
- 深、浅拷贝问题:若是使用memcpy就是浅拷贝,对于vector中是内置类型的数据可以,但是若是vector对象中涉及到自定义类型,例如string为数据的情况,浅拷贝拷贝的是地址,那么tmp中的数据的地址就是vector对象中的数据地址,之后再delete掉_start,就相当于把要存放数据的tmp中的数据给释放了,这样会让数据丢失;所以一个一个赋值,对于内置类型无影响,对于自定义类型,例如string的类型,就是赋值重载,string实现时是深拷贝,这样就解决了。
2、resize
基本思路:若是比原来的size小,那么就让_finish等于_start+n,这样就访问不到n之后的数据了;若是比原来的size大,没有给值就用匿名对象T(),对于内置类型给初始值(0、空指针、0.0一类),对于自定义类型,调用默认构造函数,实现部分:先扩容,再插入数据。
三、元素获取
T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}
重载运算符[],在内部通过数组的方式返回对应下标的位置。
四、修改
void push_back(const T& val){if (_end_of_storage == _finish){reserve(size() == 0 ? 4 : 2 * capacity());}*_finish = val;_finish++;}void pop_back(){assert(size() > 0);_finish--;}iterator Insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_end_of_storage == _finish){size_t len = pos - _start;reserve(size() == 0 ? 4 : 2 * capacity());pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *(end);end--;}*pos = val;_finish++;return pos;}iterator Erase(iterator pos){assert(pos >= _start && pos < _finish);iterator tmp = pos + 1;while (tmp < _finish){*(tmp - 1) = *tmp;tmp++;}--_finish;return pos;}
1、insert和erase调用时迭代器失效的问题
insert:实现时若是涉及到空间不够要扩容,扩容时要先记录下pos和_start之间的距离,扩容之后_start不是之前的了,那么pos也要跟着更新;此时在调用insert时若是插入之后还要访问pos就要更新pos(因为pos已经改变了,若是不更新就使用原来的pos会找不到原来的数据)
就算没有扩容,空间够,insert之后使用到pos还是要先更新,因为此时的pos指向的是新插入进来的数据,而不是我们想要访问的原来的数据。
erase:erase的迭代器失效举一个例子:
vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);container_print(v);cout << "size:" << v.size() << endl;cout << "capacity:" << v.capacity() << endl;vector<int>::iterator p = v.begin();while(p < v.end()){if (*p % 2 == 0){v.Erase(p);}p++;}container_print(v);

运行结果似乎很正确,那再看:
vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(4);v.push_back(5);container_print(v);cout << "size:" << v.size() << endl;cout << "capacity:" << v.capacity() << endl;vector<int>::iterator p = v.begin();while(p < v.end()){if (*p % 2 == 0){v.Erase(p);}p++;}container_print(v);

此时会发现有偶数没有删除完全,这是因为迭代器失效了
原因:erase的实现中删除了pos指向的数据之后返回pos,此时的pos指向的是原来要删除的数据的下一个数据,在这个删除偶数的例子中,{1,2,3,4,5}pos指向2.删除之后pos指向3,再加加就是指向4,刚好到了下一个偶数,删除之后,pos指向5,再加加,循环结束,这里只是一个巧合;数据是{1,2,3,4,4,5}删除完2,pos指向第一个4,删除之后,pos指向第二个4,再加加那么pos指向是5了,就跳过了这个4,此时就是迭代器失效了;
为了解决,erase要使用到pos就要更新迭代器;
while(p < v.end()){if (*p % 2 == 0){p=v.Erase(p);}else{p++;}}
这样写,每次删除完之后,pos指向就是原来要删除数据的下一个数据,若是这个数是偶数那就继续删,是奇数就加加跳过,这样就能够不错过数据了。
五、构造
1、强制的默认构造写法
vector() = default;//强制的默认构造
2、拷贝构造
//拷贝构造vector(const vector<T>& v){reserve(v.capacity());for (auto it : v){push_back(it);}}
3、赋值重载
void clear(){_finish = _start;}void swap(const vector<T>& tmp){std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_end_of_storage, tmp._end_of_storage);}//赋值重载(传统写法)vector<T>& operator=(const vector<T>& v){if (_start)//说明被赋值的对象不是空,要先清除{clear();}reserve(v.capacity());for (auto it : v){push_back(it);}}//赋值重载(现代写法)vector<T>& operator=(vector<T> tmp){swap(tmp);return *this;}
a、传统写法:
若是原vector不为空,先置为空,之后再扩容,扩到和v一样的大小,再使用拷贝构造那一套,将v的数据一个一个尾插进入要被赋值的vector
b、现代写法:
传参时用tmp接收赋值等式右边的vector,进行拷贝构造,之后再和赋值等式左边的vector进行交换;(现代写法一般要在拷贝构造写好的情况下进行);
4、迭代器初始化
//迭代器初始化template <class InputIterator>//可以接收不同的迭代器,但是迭代器里面的数据的类型要相同vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}
这里使用到模板是为了接收各种类型的迭代器,但是值得注意的是这些迭代器的各自的容器里面的数据类型应该和此时的待构造的vector里面的数据保持一致,再不济也可以强制类型转换;
5、n个val去初始化
//n个 val 去初始化vector(size_t n, const T& val = T())//不给值就用匿名对象,这个对象是 vector 里面的值{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}
6、析构
~vector(){if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}}
六、容器打印函数模板中的注意事项:
template <class T>void vector_print(const vector<T>& v){//要写 typename,否则没有实例化的 vector<T> 不知道const_iterator是类型还是静态成员变量typename vector<T>:: const_iterator it = v.begin();//auto it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;}
这个函数模板定义在我们自己实现的vector类外面,用来打印vector,使用到迭代器,但是在写时注意这一行:
typename vector<T>:: const_iterator it = v.begin();
要加上typename,否则编译器分不清这是类里面的静态成员变量还是类型,当然若是写成静态成员变量去使用,那就一定是静态成员变量,但是这里不能直接写,要在前面加上tyepname;第二种方法可以直接用auto自动生成类型去使用。
相关文章:
C++-vector模拟实现
###vector底层相当于是数组,查看源码可以发现,这个类的私有成员变量是三个迭代器;在实现时迭代器就可以当作是vector里面的元素的指针类型; ###vector是一个类模板,实现时也应当按照这样的写法用一个模板去实现&#…...
Activity
69[toc] 1.启停活动页面 1.Activity启动和结束 从当前页面跳到新页面 startActivity(new Intent(this, ActFinishActivity.class));从当前页面返回上一个页面,相当于关闭当前页面 finish();2.Activity生命周期 官方描述生命周期 onCreate:创建活…...
【力扣 | SQL题 | 每日四题】力扣1581, 1811, 1821, 1831
今天的题目就1811这个比较难,其他非常的基础。 1. 力扣1581:进店却未进行过交易的顾客 1.1 题目: 表:Visits ---------------------- | Column Name | Type | ---------------------- | visit_id | int | | customer…...
洛谷【P1955 [NOI2015] 程序自动分析】
反思: 这道题一眼就是并查集 但是数据太大 mle和re都是有可能的我看了题解才知道是离散化数组加并查集离散化再两个月前我觉得好难啊 那道题跟本看不懂 现在觉得还行 离散化思路: 需要一个离散记录数组----ls[N]用来记录下出现的数 步骤: …...
Swift并发笔记
1.同步和异步 说到线程的执行方式,最基本的一组概念是同步和异步。所谓同步,就是在操作执行完成之前,运行操作的这个线程都会被占用,直到函数最终被抛出或返回。Swift5.5之前,func关键字声明的所有的函数都是同步的。…...
React 组件命名规范
在 React 项目中,如果希望保持组件命名的一致性,并防止在引入时出现不同名称的问题,可以遵循以下的组件规范: 1、默认导出组件: 所有特殊要求的组件(如页面组件或根组件)应该使用 export defau…...
eNSP网络配置指南:IP设置、DNS、Telnet、DHCP与路由表管理
1.eNSP基本操作和路由器IP配置命令 登录设备:通过Console口或通过eNSP的Telnet/SSH客户端登录到设备。进入特权模式:输入system-view进入系统视图。接口配置: 进入接口视图,例如interface GigabitEthernet0/0/0。配置IP地址和子网…...
初步认识产品经理
产品经理 思考问题的维度 1️⃣为什么要抓住核心用户? 所有和产品有关系的群体就是用户,存在共性和差异了解用户的付费点,更好的优化产品是否使用:(目标用户-已使用产品:种子用户-尝鲜;核心用…...
web前端面试中拍摄的真实js面试题(真图)
web前端面试中拍摄的真实js面试题(真图) WechatIMG258.jpeg WechatIMG406.jpeg WechatIMG407.jpeg WechatIMG922.jpeg WechatIMG1063.jpeg © 著作权归作者所有,转载或内容合作请联系作者 喜欢的朋友记得点赞、收藏、关注哦!!…...
python 人工智能 机器学习 当损失函数的数值变成 `nan` 时,这通常意味着在模型训练过程中出现了数值不稳定性以及解决办法,数据分析
当损失函数的数值变成 nan 时,这通常意味着在模型训练过程中出现了数值不稳定性。以下是一些可能导致这个问题的原因以及相应的解决方法: 1. **学习率过高**:如果学习率设置得过高,可能会导致梯度爆炸,从而导致损失函…...
Kafka快速实战与基本原理详解
笔记:https://note.youdao.com/ynoteshare/index.html?id=b0357bdb4821ed2e35ecdbdacd65aa06&type=note&_time=1727570043631 启动kafka之前先启动zookper 看看ZK里面都有什么数据 : 刚开始什么数据都没有 接下来启动kafka,启动好后,日志在这里看: 启动好了kaf…...
tftp传文件被服务器拒绝进入tftp: server error: (768) Access to staonline.pcap denied
环境:测试一个ac下挂ap,ap下的抓包文件传出时,出现问题: ac的wan口ip是192.168.186.167/24,gw是192.168.186.1,下挂ap的ip是192.168.202.199/24,ac上开子接口192.168.202.1/24,ac上开…...
express,生成用户登录后的 token
在 Node.js 中使用 Express 框架生成用户登录后的 token,通常会涉及到以下几个步骤: 设置 Express 应用:首先,你需要有一个基本的 Express 应用。安装必要的中间件:例如 jsonwebtoken(JWT)用于…...
银河麒麟桌面操作系统修改默认Shell为Bash
银河麒麟桌面操作系统修改默认Shell为Bash 💐The Begin💐点点关注,收藏不迷路💐 在银河麒麟桌面操作系统(ARM版)中,若要将默认Shell从Dash改为Bash,可执行以下步骤: 打开…...
卷积神经网络(Convolutional Neural Networks, CNN)
卷积神经网络(Convolutional Neural Networks, CNN)是深度学习领域中用于处理具有网格结构的输入(如图像和视频)的神经网络模型。下面以最简单、直观的方式概述CNN的主要流程及其基本概念: 1. 输入层 概念:…...
SpringBoot系列 启动流程
文章目录 SpringApplicationSpringApplication#run 启动流程BootstrapContextSpringApplicationRunListenersprepareEnvironmentconfigureEnvironmentconfigurePropertySourcesconfigureProfiles 上下文初始化prepareContextrefreshContextprepareRefreshobtainFreshBeanFactor…...
vgg19提取特征
一般来说,大家使用VGG16,用的是第四列的网络架构,而使用VGG19,使用的就是第六列的网络架构。 使用vgg进行提取特征,在这个项目中,使用的就是每一块卷积层的第一层。 import torch.nn as nn from torchvis…...
Qt 中的 QChartView
深入理解 Qt 的 QChartView:图表展示与交互 QChartView 是 Qt Charts 模块中的一个核心类,它用于在 Qt 应用程序中显示图表,并支持多种用户交互方式。它继承自 QGraphicsView,通过封装 QChart,为用户提供了强大的图表…...
cheese安卓版纯本地离线文字识别插件
目的 cheese自动化平台是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务,节省大量人工操作的时间。可以采用Vscode、IDEA编写,支持Java、Python、nodejs、GO、Rust、Lua。cheese也包含图色功能,识别…...
【C++】多肽
目录 一 多肽定义 1. 多肽的构成条件 1 例一 2 例二 2. 虚函数 3. 虚函数重写的两个意外 1 协变 2 析构函数的重写 二 关键字override 和 final 1. final 2.override 三 三重对比 1. 练习 四 多肽的原理 1. 多肽调用和普通调用 2.虚函数表 3. 分析 4. 原理 …...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
