C++_vector类
欢迎来到本期节目- - -
vector类
本期直接先上代码,然后以代码为例介绍需要注意的问题.
模拟实现:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace my_room
{template<class T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin()const{return _start;}const_iterator cend() const{return _finish;}
//---------------------------------------------------------------------------------------------//构造和析构//无参默认构造vector() {//由于给了缺省值,所以可以给空}//带参构造vector(size_t n, const T& value = T()) {reserve(n);while (n--){push_back(value);}}//迭代器区间构造template<class InputIterator>vector(InputIterator first, InputIterator last) {size_t size = last - first;reserve(size); //提前开好空间,减少扩容次数while (first != last){push_back(*first);first++;}}//拷贝构造vector(const vector<T>& v) {vector<T> tmp(v.cbegin(), v.cend());//直接复用swap(tmp);}//赋值重载拷贝vector<T>& operator=(vector<T> v) {swap(v);return *this;}~vector(){delete[] _start; //为空也不影响_start = _finish = _endOfStorage = nullptr;}//----------------------------------------------------------------------------------------------//核心函数void reserve(size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + old_size;_endOfStorage = _start + n;}}void resize(size_t n, const T& value = T()){reserve(n);if (n <= size()){_finish = _start + n;}else{while (_finish != _start + n){*(_finish++) = value;}}}iterator insert(iterator pos, const T& x){assert(pos <= end());size_t old_posi = pos - begin();if (_finish == _endOfStorage){size_t n = capacity() == 0 ? 4 : capacity() * 2;reserve(n);}iterator tail = end();pos = begin() + old_posi;while (tail > pos){*tail = *(tail - 1);tail--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos < end());iterator begin = pos+1;while (begin != _finish){*(begin-1) = *begin;begin++;}_finish--;return pos;}//----------------------------------------------------------------------------------------------//简单函数size_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}bool empty()const{return _start == _finish;}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& x){insert(end(), x);}void pop_back(){erase(end() - 1);}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _endOfStorage = nullptr; // 指向存储容量的尾};
}
核心函数
reserve
首先我们看看reserve函数
当我们在实现vector时,我们都知道,实现异地扩容都要经历这个流程:
注意
在这其中,拷贝数据就需要注意,在以往的顺序表中,我们可能会用memcpy来拷贝数据,当然在内置类型的情况下,memcpy不仅不会出错,而且比赋值拷贝高效; |
但是这里是类模板,数据类型T可以是自定义类型,就有可能需要深拷贝,而memcpy本质上是浅拷贝,不满足需求,所以使用赋值重载拷贝完成自定义类型的拷贝. |
除此之外,还需要注意的是更新属性问题,
_finish = _start+size(); //size()返回的是 (_finish - _start)的值
此时_finish指向旧空间,_start指向新空间,返回的值根本不是有效数据的个数; |
所以需要在扩容之前记录一个old_size记录数据个数,使_finish指向有效数据的末尾. |
void reserve(size_t n){if (n > capacity()){size_t old_size = size(); //记录old_size是为了不影响有效数据末尾的正确计算T* tmp = new T[n];for (size_t i = 0; i < old_size; i++) {tmp[i] = _start[i]; //使用赋值拷贝是为了满足有深拷贝的自定义类型对象}delete[] _start;_start = tmp;_finish = _start + old_size;_endOfStorage = _start + n;}}
resize
接下来瞧瞧resize
resize只需要注意,该函数会改变有效数据的个数以及有可能会扩容.
void resize(size_t n, const T& value = T())
{reserve(n); //管他容量够不够,直接让reserve去判断.if (n <= size()){_finish = _start + n; //直接删除数据}else{while (_finish != _start + n) {*(_finish++) = value; //添加数据}}
}
insert
接下来瞅瞅insert
在实现insert函数时,核心逻辑就是移动数据.
iterator insert(iterator pos, const T& x)
{assert(pos <= end()); //注意可以尾插,所以取等size_t old_posi = pos - begin(); //由于可能需要扩容,所以会导致迭代器指向旧空间,提前记录相对位置if (_finish == _endOfStorage){size_t n = capacity() == 0 ? 4 : capacity() * 2;reserve(n);}iterator tail = end();pos = begin() + old_posi; //更新迭代器while (tail > pos){*tail = *(tail - 1); //从后往前挨个 向后移动tail--;}*pos = x; //插入数据_finish++;return pos;
}
erase
最后瞄一下erase
iterator erase(iterator pos)
{assert(pos < end()); //_finish没有数据,不能取等iterator begin = pos+1;while (begin != _finish){*(begin-1) = *begin; //从前往后挨个 向前移动begin++;}_finish--;return pos;
}
迭代器
迭代器:
迭代器屏蔽了底层的实现细节,使得设计人员无需关心容器对象的内存分配就可以直接使用相应接口,达到类似指针的效果.
也就是说迭代器可能是指针,也可能是指针的封装.
上图中迭代器支持的行为是向下兼容的,
假设p1,p2是迭代器,i是常量
Iterator | 描述 | 行为 |
---|---|---|
随机迭代器 | 支持随机访问的迭代器 | p1++,p1- -,p1+i,p1-i,p1==p2,p1>p2,p1<p2 |
双向迭代器 | 可以向前或者向后移动的迭代器 | p1++,p1- -,p1==p2 |
前向迭代器 | 只能向前移动的迭代器 | p1++,p1==p2 |
输入迭代器 | 只能向前移动并读取的迭代器 | p1++,p1==p2 |
输出迭代器 | 只能向前移动并写入的迭代器 | p1++ ,p1==p2 |
很明显咱们的vector的迭代器是随机迭代器.
迭代器失效
什么是迭代器失效?
迭代器所指向的节点无效了,既该节点被删除了.
vector迭代器失效场景
- 场景1:扩容
void test()
{my_room::vector<int> arr;for(int i = 0; i < 10; i++) //存入1到10{arr.push_back(i+1);}my_room::vector<int>::iterator old_back = arr.end()-1; //指向最后一个元素10arr.reserve(1000);cout<< *old_back <<endl; //程序崩溃----error代码,请勿模仿
}
扩容之后,old_back指向的空间已经被释放了,此时相当于使用野指针. |
事实上,不一定只能通过直接显示调用reserve导致迭代器失效,也可以间接调用reserve发生扩容行为导致迭代器失效; |
例如: 使用resize函数可能会扩容,使用insert,push_back函数也可能扩容 |
总的来说,就是底层涉及扩容的行为,就需要注意迭代器失效的问题. |
- 场景2:删除
void test()
{my_room::vector<int> arr;for(int i = 0; i < 10; i++) //存入1到10{arr.push_back(i+1);}my_room::vector<int>::iterator start = arr.begin(); //指向第一个元素1while(start != arr.end()) //预期删除所有元素{arr.erase(start); //------error代码,请勿模仿}cout << arr.size(); //看看有没有删完,但实际上在vs中直接报错.
}
本来预期的是start一开始指向第一个元素,删除之后,剩下的元素往前移,然后继续删,直到删完. |
但是实际上删除行为同样也有迭代器失效的风险. |
迭代器失效解决方案
我们知道在使用vector时,当接口涉及扩容问题或者删除问题时,会有迭代器失效的问题.
解决办法就是使用之前对迭代器重新赋值.
void test()
{my_room::vector<int> arr;for(int i = 0; i < 10; i++){arr.push_back(i+1);}my_room::vector<int>::iterator old_back = arr.end()-1; arr.reserve(1000);old_back = arr.end()-1;cout<< *old_back <<endl; //程序正常运行
}
void test()
{my_room::vector<int> arr;for(int i = 0; i < 10; i++) {arr.push_back(i+1);}my_room::vector<int>::iterator start = arr.begin();while(start != arr.end()){start = arr.erase(start); }cout << arr.size(); //程序正常运行
}
希望本片文章对您有帮助,请点赞支持一下吧😘💕
相关文章:

C++_vector类
欢迎来到本期节目- - - vector类 本期直接先上代码,然后以代码为例介绍需要注意的问题. 模拟实现: #pragma once #include<iostream> #include<assert.h> using namespace std;namespace my_room {template<class T>class vector{p…...

Spring Boot入门到精通:网上购物商城系统
第3章 系统分析 3.1 可行性分析 在系统开发之初要进行系统可行分析,这样做的目的就是使用最小成本解决最大问题,一旦程序开发满足用户需要,带来的好处也是很多的。下面我们将从技术上、操作上、经济上等方面来考虑这个系统到底值不值得开发。…...

在Vue.js中,你可以使用Element UI的el-input组件结合计算属性来实现模糊查询
<template><div><el-input v-model"searchQuery" placeholder"请输入查询内容"></el-input><div v-for"item in filteredList" :key"item">{{ item }}</div></div> </template><s…...

delphi制作漂亮的农历窗体(IntraWeb+Layui的完美结合)
delphi制作漂亮的农历窗体(IntraWebLayui的完美结合) 不需要安装服务器,Apache和IIS都不需要,自带企业级服务器。 运行exe服务器就架好了,直接打开手机浏览器或者电脑浏览器,网页就出来了,如果…...

发票OFD格式转换成PDF
引入依赖,低版本的报错,2.0.2能够实现转换 <dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.0.2</version><exclusions><exclusion><groupId&g…...

高通AI应用程序开发3:网络模型(一)
1. 支持的网络模型 Qualcomm神经处理SDK支持下表所列的网络模型。 有关支持的运行时和单个图层类型的限制和约束的详细信息,请参阅 限制 。 GPU运行时中支持的所有层对两种GPU模式都有效:GPU_FLOAT32_16_HYBRID和GPU_FLAAT16。GPU_FLOAT32_16_HYBRID-…...

03. 前端面试题之ts : typescript 的数据类型有哪些?
文章目录 一、typescript是什么二、typescript有哪些数据类型booleannumberstringarraytupleenumanynull 和 和 undefinedvoidneverobject 三、总结 一、typescript是什么 typescript 和 javascript几乎一样,拥有相同的数据类型,另外在javascript基础上…...

PyCharm和VS Code 安装通义灵码,可本地安装包安装,解决插件安装不上问题
PyCharm和VS Code 安装通义灵码,可本地安装包安装,解决插件安装不上问题 PyCharm、VS Code 安装通义灵码介绍主要应用场景支持编程语言安装指南JetBrains IDEs 中安装指南步骤 1:准备工作步骤 2:在 JetBrains IDEs 中安装通义灵码…...
机器人速度雅可比矩阵求解(2自由度平面关节机器人)
关节速度和末端速度空间的映射需要计算雅可比矩阵的逆矩阵,在博途PLC里如何计算一个方阵的逆矩阵,大家可以参考下面这篇文章: 博途PLC矩阵求逆 矩阵求逆 博图SCL_博图矩阵运算-CSDN博客文章浏览阅读839次。本文介绍如何用C语言实现矩阵求逆的过程,详细解析了相关代码,适…...

【AI大模型-文心-思维树解读-开篇】
提问:什么是“”“思维树”“”模型框架 回答:如下 版本:文心大模型3.5 “思维树”(Tree of Thoughts, ToT)模型框架是一个利用大型语言模型进行问题解决的框架。它借鉴了人类认知研究的成果,特别是关于人…...

2、electron vue3 怎么创建子窗口,并给子窗口路由传参
接上回初始化vue3 electron项目,创建完vue3 electron项目后,现在要实现在渲染进程中点击按钮创建一个新的子窗口 开始 子窗口创建操作只能在主线程内完成,而创建操作是在渲染线程触发,因此就需要进行两者间的通讯。 1、创建子窗…...

8.pod数据持久化
💂 个人主页: Java程序鱼 💬 如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)和订阅专栏 👤 微信号:hzy1014211086,想加入技术交流群的小伙伴可以加我好友,群里会分享学习资料、学习方法…...

C语言 | Leetcode C语言题解之第436题寻找右区间
题目: 题解: typedef struct {int start;int index; } Node;int cmp(const void *pa, const void *pb) {return ((Node *)pa)->start - ((Node *)pb)->start; }int* findRightInterval(int** intervals, int intervalsSize, int* intervalsColSiz…...

SpringBoot3中ymal配置文件(持续更新)
博客主页:音符犹如代码系列专栏:JavaWeb关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 在SpringBoot项目中,使用application.properties进行配置管理时,…...

Linux 基础IO 2
读取与写入 read与fread 在基础IO 1中我们学会了open和fopen的函数这两个函数是用于为进程打开文件也可以理解为为进程和文件建立了一个链接使其可以交互。那我们建立号链接之后肯定还是需要对文件进行操作,现在我们先来了解读取操作。 read: 这是一…...

图像预处理 图像去噪之常见的去噪方法
图像去噪是图像预处理中的一项关键技术,其目的是从含有噪声的图像中恢复出无噪声的图像,以提高图像质量和后续图像分析的准确性。图像去噪方法众多,本文将介绍几种常见的去噪方法,并提供相应的代码示例。 1. 均值滤波(…...

代码随想录Day53|102.沉没孤岛 、103.水流问题 、104.建造最大岛屿
102.沉没孤岛 import java.util.*;class Main{public static int[][] dir {{0,1},{1,0},{0,-1},{-1,0}};public static void main (String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int m sc.nextInt();int[][] grid new int[n][m];for(int i 0…...

19c-pfile
经常需要rman恢复测试,创建一个单机pfile,需要时手动修改使用,以20g内存为例 orcl.__data_transfer_cache_size0 orcl.__db_cache_size13824425984 orcl.__inmemory_ext_roarea0 orcl.__inmemory_ext_rwarea0 orcl.__java_pool_size0 orcl._…...

智能软件开启精准品牌控价
在当今竞争激烈的商业世界中,品牌的价值如同璀璨的明珠,需要精心呵护。而价格管控,则是守护这颗明珠的关键防线。 当面对众多的产品和 SKU 时,传统的人力监测已显得力不从心。此时,力维网络自主开发的数据监测系统如同…...

OpenCV特征检测(8)检测图像中圆形的函数HoughCircles()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在灰度图像中使用霍夫变换查找圆形。 该函数使用霍夫变换的一种修改版本在灰度图像中查找圆形。 例子: #include <opencv2/imgp…...

spark 大表与大表join时的Shuffle机制和过程
在 Spark 中,当处理大表与大表的 JOIN 操作时,通常会涉及到 Shuffle 机制,这是分布式计算中用于重新分布数据的关键步骤。Shuffle 的本质是将数据按照某种方式重新分组,使得相同 key 的数据能够被发送到同一个计算节点进行后续的操…...

大厂面试真题:简单说下Redis的bigkey
什么是bigkey bigkey是指key对应的value所占的内存空间比较大,例如一个字符串类型的value可以最大存到512MB,一个列表类型的value最多可以存储23-1个元素。 如果按照数据结构来细分的话,一般分为字符串类型bigkey和非字符串类型bigkey。 字…...
18 vue3之自动引入ref插件深入使用v-model
自动引入插件后无需再引入ref等 使用自动引入插入无需在import { ref, reactive } from "vue"做这样的操作 npm i unplugin-auto-import - D vite配置 import AutoImport from unplugin-auto-import/vite //使用vite版本 export default defineConfig({plugins: [v…...

【Spring】lombok、dbUtil插件应用
一、lombok插件 1. 功能:对实体类自动,动态生成get、set方法,无参、有参构造..... 2. 步骤: (1)idea安装插件(只做一次) (2)添加坐标 (3)编写注解 NoArgsCo…...

【学习笔记】WSL
WSL 1、 介绍 1.1、概述 1.2、版本 1.3、配置安装 2、 基本 2.1、基本命令 1、 介绍 1.1、概述 WSL 是 Windows Subsystem for Linux 的缩写,中文称为 Windows 下的 Linux 子系统。它是微软在 Windows 上提供的一种功能,允许用户在 …...

python assert 断言用法
语法: try:assert 条件表达式, "可选的错误消息" except AssertionError as error:print(f"断言失败:{error}")其中, try...except是异常处理语法结构,try可以测试代码块中的错误,并在出现异常时…...

MySQL事务、索引、数据恢复和备份
MySQL事务、索引、数据恢复和备份 1.MySQL的事务处理 事务就是将一组SQL语句放在同一批次内去执行 如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行 MySQL的事务实现方法 : SET AUTOCOMMIT 使用SET语句来改变自动提交模式 SET AUTOCOMMIT 0; # 关…...

什么是chatgpt?国内有哪些类gpt模型?
什么是ChatGPT? “ChatGPT”这个名字越来越多地出现在我们的生活中。简单来说,ChatGPT是OpenAI开发的一种人工智能对话模型。它基于GPT(Generative Pre-trained Transformer,生成式预训练变换模型)架构,能…...

ISP基本框架及算法介绍 ISP(Image Signal Processor)
ISP基本框架及算法介绍 ISP(Image Signal Processor),即图像处理,主要作用是对前端图像传感器输出的信号做后期处理,主要功能有线性纠正、噪声去除、坏点去除、内插、白平衡、自动曝光控制等,依赖于ISP才能在不同的光学条件…...

Stable Diffusion 的 ControlNet 主要用途
SD(Stable Diffusion)中的ControlNet是一种条件生成对抗神经网络(Conditional Generative Adversarial Network, CGAN)的扩展技术,它允许用户通过额外的输入条件来控制预训练的大模型(如Stable Diffusion&a…...