当前位置: 首页 > article >正文

vector模拟实现2

文章目录

  • vector的模拟实现
    • erase函数
    • resize
    • 拷贝构造
    • 赋值重载
    • 函数模版构造及其细节
    • 结语

我们今天又见面啦,给生活加点impetus!!开启今天的编程之路
在这里插入图片描述
今天我们来完善vector剩余的内容,以及再探迭代器失效!
作者:٩( ‘ω’ )و260
我的专栏:C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!

vector的模拟实现

erase函数

在这里我们会见识到第二种迭代器失效
先来讲解这种算法的思路:首先删除一个元素,我们就要让这个元素后面所有的元素向前一步,把这个元素给覆盖掉,注意特殊情况:头删和尾删。
我们直接来看代码实现:

void erase(iterator pos)
{assert(pos>=_start&&pos<_finish);//因为_start这个位置可以取到,_finish这个位置娶不到,因为是左闭右开区间iterator it=pos+1;while(it<_finish){*(it-1)=*it;//后面位置的元素赋值给前一个元素it++;//迭代器向后遍历}_finish--;//之减了一个元素,所以_finish只用减一个就可以了
}

我们来测试一下:

void test_vector5()
{Mrzeng::vector<int> v = { 1,2,2,2,3,4,5,6,7,9,8 };//删除为偶数的元素for (auto& e : v){cout << e << " ";}cout << endl;Mrzeng::vector<int>::iterator it = v.begin()+5;erase(it);for(auto& e:v){cout<<e<<" ";//输出1 2 2 2 2 5 6 7 8 9}

此时我们能够得出正确的结果
我们再来把测试用例改的特殊些:
如果我们要把偶数全部给删除掉,那我们应该写一个循环,遍历到偶数的时候就删除:
来看代码:

	Mrzeng::vector<int>::iterator it = v.begin();while (it < v.end()){if (*it % 2 == 0){v.erase(it);//erase迭代器的元素之后这个迭代器一定会失效,所以需要赋值重新修改迭代器}}//我们只是将上述代码中循环的这一个部分给修改了,其余部分还是没有修改

此时我们来运行一下代码:
我们发现产生了问题,程序异常退出了,那这个是什么原因呢?

这里就是我们所说的第二种迭代器失效,第一种我们所说的迭代器失效是因为扩容导致了空指针的问题,这里是因为erase的问题造成的

其实如果我们把这个代码放到g++编译器下,这个代码就是正确的,在msvc编译器中,每次erase迭代器位置的值之后编译器都会对这个迭代器进行强制检查,而g++不会对这个迭代器进行强制检查。同时,这个迭代器erase之后就会立即失效,那为啥msvc编译器下有这样的一种机制呢?而在g++编译器下没有这种机制?

假设没有这种机制,我们能够一直来访问这个迭代器,当我一直删除数据的时候,此时容量减少,有没有可能会造成缩容的情况,如果大幅缩容,此时又会开辟新的空间,此时迭代器失效又会回归到第一种类型,那么我不如直接就进行erase位置进行检查。

总结:erase函数中的迭代器使用后会立即失效,不能再利用这个迭代器去遍历我们的vector
那我们应该如何来解决呢?
既然erase迭代器使用后立即失效,那我们就直接重新给这个迭代器赋值。
此时我们将erase(it)这句代码改为:

it=erase(it);//此时我们给it重新赋值

既然此时我们erase函数都有返回值,那我们也要修改erase的整体函数:
erase的最终代码为:

iterator erase(iterator pos)
{assert(pos>=_start&&pos<_finish);iterator it=pos+1;while(it<_finish){*(it-1)=*it;it++;}_finish--;return pos;
}

为什么我要返回pos呢?因为vector规定,erase之后要返回删除位置的下一个元素,此时我们将pos位置的元素删除,后面的元素是不是走到前面来了,占了pos位置的元素,所以我们这里要返回pos位置的元素

总结:
1: vs下认为erase后it失效,失效迭代器进行强制检查,访问既会报错
2::g++编译器不会对erase位置的迭代器进行强制检查
3:迭代器必须要重新赋值之后才能够继续使用

其实我们还可以来扩展一个知识点:
利用erase来进行缩容其实是以时间换空间的做法,其实这样不是很适合,因为我们现在电脑的空间都是比较富裕,没有必要为了一点空间而消耗大量时间,而一般的算法其实都是以空间换时间

resize

接下来我们来实现resize,本质上有三种方法:假设我们resize传递过来的参数是n。
如果n>size()&&n<capacity(),只是插数据,如果n>capacity(),扩空间的同时再来插数据。如果n<size(),就会造成缩容。

其实最后我们可以把这个分解成两个情况,插数据和不插数据:
我们直接来看代码实现:

void resize(size_t n,cosnt T& val=T())//使用了隐式类型转换,上一篇文章我们已经讲过了
{assert(n>=0);if(n>size())//扩容{reverse(n);while(_finish!=_end_of_storage)//循环插入数据{push_back(val);}}else{//缩容_finish=_start+n;//修改_finish就可以了}
}

拷贝构造

我们先要创建空间,将实参的内容拷贝给这个空间。
例如:

vector(const vector<T>& v)
{reverse(v.capacity);for(auto& e:v){push_back(e);}
}

由于这里涉及到了reverse,我么这里再来补充一个点,我们以一个例子来开头:

void test_vector9()
{Mrzeng::vector<string> v;v.push_back("111111111111111111");v.push_back("111111111111111111");v.push_back("111111111111111111");v.push_back("111111111111111111");v.push_back("111111111111111111");for (auto& e : v){cout << e << " ";}
}

来看这一个代码,我们来运行看一下结果:
在这里插入图片描述
为什么这里会打印出这个?首先我先说明一下,有些时候我们打印出绒绒绒绒绒,烫烫烫等的原因:

在 C 和 C++ 里,若你声明了一个数组或者分配了一块内存,却没有对其进行初始化,那么这块内存区域就会保留原有的数据。在 Windows 系统下,当使用 Visual Studio 编译器进行调试时,未初始化的栈内存会被填充为 0xCC。而 0xCCCC 在 GBK 编码里代表汉字 “烫”。

我们再来看一下这里如果我们只是插入4个数据的结果:
在这里插入图片描述
敏锐的你一定会发现,肯定是这里的扩容步骤出了问题。
这里可以补充一个知识点:代码没有达到预期结果的时候,如果是编译和运行时的错误,此时排除错误的方法只能够利用排除法,先屏蔽掉一部分的代码,随后看整体代码是否报错
这里我们直接来讲结论吧
首先,扩容肯定就会开空间,此时就一定会有新旧指针,而且,我们对旧空间就指针进行析构之后,我们发现旧空间影响了新空间,所以,这里应该不难想到,这里是新旧指针指向了同一块空间
来看下图解:
在这里插入图片描述

那为什么我们这里是指向了同一块空间呢?就是因为memcpy是浅拷贝。有资源的话进行浅拷贝会造成多个指针指向同一块空间。所以我们这里只能手动实现深拷贝,来看下列代码:

void reverse(size_t n)
{if(n>capacity()){T* tmp=new T[n];size_t old_size=_finish-_start;if(_start){	//memcpy(tmp,_start,sizeof(T)*old_size);for(size_t i=0;i<size();i++){tmp[i]=_start[i];//一个元素一个元素来进行拷贝}delete _start;	}_start=tmp;_finish=_start+old_size;_end_of_storage=_start+n;}
}

总结:

1:扩容的时候最好还是不要用memcpy来进行数据拷贝,因为我无法确定vector存储的元素是不是包含有资源的,如果有资源的话,必须要实现深拷贝。
2:深拷贝:扩容可能存在,拷贝构造可能存在,因为默认生成的拷贝构造是浅拷贝。
3:tmp[i]=_start[i];代码解释:等价于string1=string2,是调用库中的拷贝构造来完成赋值操作的,因为赋值重载会调用拷贝构造

赋值重载

我们这里以前传统的写法其实就是将数据给他拷贝过去。但是我们这里使用一种比较常用的方法
来看一下代码:

void swap(vector<T> v)
{std::swap(_start,v._start);std::swap(_finish,v._finish);std::swap(_finish,v._finish);
}
vector<T>& operator=(vector<T> v)//这里使用传值调用,这样就直接调用拷贝构造了
{swap(v);//调用算法库中的swap函数,因为效率问题,我们最好就只用算法库中的swap函数来交换内置类型return *this;
}

但是这样的话就有一个缺点,万一我们赋值左右操作数都是同一个数,就会造成返回错误,因为在重载函数之后,v会释放掉函数栈帧,析构的话也会造成this指针指向的内容受到影响,但是一般我们不会这样做
或者我们也可以将代码改为这样:

vector<T>& operator=(vector<T>& v)//这里使用传值调用,这样就直接调用拷贝构造了
{if(this!=&v){vector<T> tmp(v);//拷贝构造一份vswap(v);//调用算法库中的swap函数,因为效率问题,我们最好就只用算法库中的swap函数来交换内置类型}return *this;
}

总结:语法规定,构造函数是一定会调用拷贝构造的

函数模版构造及其细节

接下里我们来利用函数模版构造vector。
首先:为什么我们这里是需要模版来构造vector,是因为我们可能使用其他容器中的数据来初始化vector。
其次,函数想要传递模版参数,必须事先要有模版声明
我们来写一下这个部分的代码:

template<class InputIterator>
vector(InputIterator first,InputIterator last)
{while(first!=last){push_back(*first);first++;}
}

那么是不是我们这个代码就没有问题了呢?其实不是的,我们这个代码仍然有问题:
倘若我们给构造函数这样传递一个参数:

vector<int> v(10,1);//这里我们想使用10个1来初始化这个vector

我们此时的目标是想要调用这个构造函数:

		vector(size_t n, const T& val = T())//这里使用了匿名对象{assert(n > 0);//因为使用匿名对象一定会调用构造函数,但是在函数模版之后,内置类型也有构造函数,使用匿名对象时调用构造函数内置类型会被升级成自定义类型reverse(n);//查看空间是否足够for (size_t i = 0;i < n;i++){push_back(val);}}

但是此时我们发现会出一个问题,来看:
在这里插入图片描述
我们来画图理解一下:
在这里插入图片描述
也许你这里会去钻空子,那我们这里将size_t改为int不就好了吗?
但是还有一个极端的情况,如果我们参数传递写为:

vector(10u,1);

字符后面带u的这种极端情况,此时这个会被编译器默认认为是unsigned类型的数据,此时又会调用到模版,又会产生同样的问题,那我们这个应该怎么办呢?
其实,为了防止这种情况的发生,我们就可以直接让着两种情况都给他加上去就好了。
来看这个部分的所有代码:

在这里插入代码片		vector(size_t n, const T& val = T())//这里使用了匿名对象{assert(n > 0);//因为使用匿名对象一定会调用构造函数,但是在函数模版之后,内置类型也有构造函数,使用匿名对象时调用构造函数内置类型会被升级成自定义类型reverse(n);//查看空间是否足够for (size_t i = 0;i < n;i++){push_back(val);}}vector(int n, const T& val = T())//这里使用了匿名对象{reverse(n);for (int i = 0;i < n;i++){push_back(val);}}//为什么要这个函数模版,因为防止和上面的使用冲突了template<class InputIterator>//想要使用函数模版,必须先要对这个模版进行声明vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}

总结:函数传参会调用类型更加匹配的一个,如果没有类型匹配的,可能会进行隐式类型转换,比如int转变为size_t

结语

感谢大家阅读我的博客,不足之处欢迎留言指正!!
三更灯火五更鸡,正是男儿读书时,加油,少年!!
在这里插入图片描述

相关文章:

vector模拟实现2

文章目录 vector的模拟实现erase函数resize拷贝构造赋值重载函数模版构造及其细节结语 我们今天又见面啦&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路 今天我们来完善vector剩余的内容&#xff0c;以及再探迭代器失效&#xff01; 作者&#xff…...

观察者模式在Java单体服务中的运用

观察者模式主要用于当一个对象发生改变时&#xff0c;其关联的所有对象都会收到通知&#xff0c;属于事件驱动类型的设计模式&#xff0c;可以对事件进行监听和响应。下面简单介绍下它的使用&#xff1a; 1 定义事件 import org.springframework.context.ApplicationEvent;pu…...

详解相机的内参和外参,以及内外参的标定方法

1 四个坐标系 要想深入搞清楚相机的内参和外参含义&#xff0c; 首先得清楚以下4个坐标系的定义&#xff1a; 世界坐标系&#xff1a; 名字看着很唬人&#xff0c; 其实没什么大不了的&#xff0c; 这个就是你自己定义的某一个坐标系。 比如&#xff0c; 你把房间的某一个点定…...

在线sql 转 rust 模型(Diesel、SeaORM),支持多数据 mysql, pg等

SQL 转 Rust 在 Rust 语言中&#xff0c;常用 Diesel 和 SeaORM 进行数据库操作。手写 ORM 模型繁琐&#xff0c;gotool.top 提供 SQL 转 Diesel、SeaORM 工具&#xff0c;自动生成 Rust 代码&#xff0c;提高开发效率。 特色 支持 Diesel / SeaORM&#xff0c;生成符合规范…...

高并发内存池(二):Central Cache的实现

前言&#xff1a;本文将要讲解的高并发内存池&#xff0c;它的原型是Google的⼀个开源项⽬tcmalloc&#xff0c;全称Thread-Caching Malloc&#xff0c;近一个月我将以学习为目的来模拟实现一个精简版的高并发内存池&#xff0c;并对核心技术分块进行精细剖析&#xff0c;分享在…...

[Windows] VutronMusic v1.6.0 音乐播放器纯净版,可登录同步

VutronMusic-简易好看的PC音乐播放器 链接&#xff1a;https://pan.xunlei.com/s/VOMq7P_fTyhLUXeGerDVhrCTA1?pwduvut# VutronMusic v1.6.0 音乐播放器纯净版&#xff0c;可登录同步...

macvlan 和 ipvlan 实现原理及设计案例详解

一、macvlan 实现原理 1. 核心概念 macvlan 允许在单个物理网络接口上创建多个虚拟网络接口&#xff0c;每个虚拟接口拥有 独立的 MAC 地址 和 IP 地址。工作模式&#xff1a; bridge 模式&#xff08;默认&#xff09;&#xff1a;虚拟接口之间可直接通信&#xff0c;类似交…...

【蓝桥杯】每日练习 Day19,20

目录 前言 蒙德里安的梦想 分析 最短Hamilton路径 分析 代码 乌龟棋 分析 代码 松散子序列 分析 代码 代码 前言 今天不讲数论&#xff08;因为上课学数论真是太难了&#xff0c;只学了高斯消元&#xff09;所以今天就不单独拿出来讲高斯消元了。今天讲一下昨天和…...

《AI大模型应知应会100篇》第7篇:Prompt Engineering基础:如何与大模型有效沟通

第7篇&#xff1a;Prompt Engineering基础&#xff1a;如何与大模型有效沟通 摘要 Prompt Engineering&#xff08;提示工程&#xff09;是与大模型高效沟通的关键技能。通过精心设计的Prompt&#xff0c;可以让模型生成更准确、更有用的结果。本文将从基础知识到高级策略&…...

微服务架构技术栈选型避坑指南:10大核心要素深度拆解

微服务架构的技术栈选型直接影响系统的稳定性、扩展性和可维护性。以下从10大核心要素出发&#xff0c;结合主流技术方案对比、兼容性评估、失败案例及优化策略&#xff0c;提供系统性选型指南。 1. 服务框架与通信 关键考量点 扩展性&#xff1a;框架需支持定制化扩展&#x…...

Elasticsearch 正排索引

一、正排索引基础概念 在 Elasticsearch 中&#xff0c;正排索引用于存储完整的文档内容&#xff0c;以便通过文档ID 快速定位文档的字段值。正排索引通过 Doc Values 和 Store Fields 两种形式&#xff0c;为聚合、排序、脚本计算等场景提供高效支持。Doc Values 的列式存储设…...

Spring实现WebScoket

SpringWeb编程方式分为Servlet模式和响应式。Servlet模式参考官方文档&#xff1a;Web on Servlet Stack :: Spring Framework&#xff0c;响应式&#xff08;Reacive&#xff09;参考官方文档&#xff1a;Web on Reactive Stack :: Spring Framework。 WebSocket也有两种编程方…...

Token是什么?

李升伟 整理 “Token” 是一个多义词&#xff0c;具体含义取决于上下文。以下是几种常见的解释&#xff1a; 1. 计算机科学中的 Token 定义&#xff1a;在编程和计算机科学中&#xff0c;Token 是源代码经过词法分析后生成的最小单位&#xff0c;通常用于编译器和解释器。 …...

odoo-045 ModuleNotFoundError: No module named ‘_sqlite3‘

文章目录 一、问题二、解决思路 一、问题 就是项目启动&#xff0c;本来好好地&#xff0c;忽然有一天报错&#xff0c;不知道什么原因。 背景&#xff1a; 我是在虚拟环境中使用的python3.7。 二、解决思路 虚拟环境和公共环境直接安装 sqlite3 都会报找不到这个库的问题…...

cesium加载CTB生成的地形数据

由于CTB生成的地形数据是压缩的&#xff08;gzip&#xff09;格式&#xff0c;需要在nginx加上特殊配置才可以正常加载&#xff0c;NGINX全部配置如下 worker_processes 1; events {worker_connections 1024; } http {include mime.types;default_type application/o…...

前端JS高阶技法:序列化、反序列化与多态融合实战

✨ 摘要 序列化与反序列化作为数据转换的核心能力&#xff0c;与多态这一灵活代码设计的核心理念&#xff0c;在现代前端开发中协同运作&#xff0c;提供了高效的数据通信与扩展性支持。 本文从理论到实践&#xff0c;系统解析&#xff1a; 序列化与反序列化的实现方式、使用…...

TS中的Class

基本用法 implements implements 关键字用于传递对类产生约束的数据类型 interface AnimalInfo{name:stringrace:stringage:number }interface AnimalCls{info:AnimalInfosayName():void} class Animal implements AnimalCls{info:AnimalInfoconstructor(info:AnimalInfo) {t…...

RustDesk 开源远程桌面软件 (支持多端) + 中继服务器伺服器搭建 ( docker版本 ) 安装教程

在需要控制和被控制的电脑上安装软件 github开源仓库地址 https://github.com/rustdesk/rustdesk/releases 蓝奏云盘备份 ( exe ) https://geek7.lanzouw.com/iPf592sadqrc 密码:4esi 中继服务器设置 使用docker安装 sudo docker image pull rustdesk/rustdesk-server sudo…...

【计网速通】计算机网络核心知识点与高频考点——数据链路层(二)

数据链路层核心知识点&#xff08;二&#xff09; 涵盖局域网、广域网、介质访问控制&#xff08;MAC层&#xff09;及数据链路层设备 上文链接&#xff1a;https://blog.csdn.net/weixin_73492487/article/details/146571476 一、局域网&#xff08;LAN,Loacl Area Network&am…...

STM32单片机入门学习——第3-4节: [2-1、2]软件安装和新建工程

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.01 STM32开发板学习——第一节&#xff1a; [1-1]课程简介 前言开发板说明引用解答和…...

W3C XML Schema 活动

W3C XML Schema 活动 概述 W3C XML Schema(XML Schema)是万维网联盟(W3C)定义的一种数据描述语言,用于定义XML文档的结构和约束。XML Schema为XML文档提供了一种结构化的方式,确保数据的一致性和有效性。本文将详细介绍W3C XML Schema的活动,包括其发展历程、主要特点…...

爬虫【Scrapy-redis分布式爬虫】

Scrapy-redis分布式爬虫 1.Scrapy-redis实现增量爬虫 增量爬虫的含义 就是前面所说的的暂停、恢复爬取 安装 # 使用scrapy-redis之前最好将scrapy版本保持在2.8.0版本, 因为2.11.0版本有兼容性问题 pip install scrapy==2.8.0 pip install scrapy-redis -i https://pypi.tun…...

intellij Idea 和 dataGrip下载和安装教程

亲测有效 第一步&#xff1a;卸载老版本idea/Datagrip &#xff08;没有安装过的可跳过此步骤&#xff09; 第二步&#xff1a;下载idea/dataGrip安装包 建议选择2022以后的版本 官网&#xff1a; https://www.jetbrains.com/datagrip/download/other.html 选择dataGrip 的…...

轻量级搜索接口技术解析:快速实现关键词检索的Java/Python实践

Hi&#xff0c;你好&#xff01; 轻量级搜索接口技术解析&#xff1a;快速实现关键词检索的Java/Python实践 接口特性与适用场景 本接口适用于需要快速集成搜索能力的开发场景&#xff0c;支持通过关键词获取结构化搜索结果。典型应用场景包括&#xff1a; 垂直领域信息检索…...

架构设计基础系列:事件溯源模式浅析

图片来源网络&#xff0c;侵权删 ‌1. 引言‌ ‌1.1 研究背景‌ 传统CRUD模型的局限性&#xff1a;状态覆盖导致审计困难、无法追溯历史。分布式系统复杂性的提升&#xff1a;微服务架构下数据一致性、回滚与调试的需求激增。监管合规性要求&#xff1a;金融、医疗等领域对数…...

ResNet系列和ViT系列预训练模型权重文件下载

一、简单介绍 OpenAI CLIP项目提供的预训练模型权重文件列表&#xff0c;主要包含两种架构系列和不同规模配置&#xff1a; ResNet系列 (RN) 基础版本&#xff1a;RN50&#xff08;ResNet-50&#xff09;扩展版本&#xff1a;RN50x4、RN50x16、RN50x64&#xff08;宽度扩展&am…...

【力扣hot100题】(035)二叉树的中序遍历

正常方法递归很简单&#xff0c;于是又学了一种栈的方法。 原理如下&#xff1a;每次循环先尽量将目前节点入栈并左移&#xff0c;没有左节点时回到栈首节点将目前节点放入结果容器中并移出栈外&#xff0c;目前节点变为该节点的右节点&#xff0c;循环结束条件是目前节点为nu…...

《数字图像处理》教材寻找合作者

Rafael Gonzalez和Richard Woods所著的《数字图像处理》关于滤波器的部分几乎全错&#xff0c;完全从零开始写&#xff0c;困难重重。关于他的问题已经描述在《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》。 现寻找能够共同讨论、切磋、…...

批量删除 txt/html/json/xml/csv 等文本文件中的重复行

在文本文件中&#xff0c;可能会存在一些重复的数据行&#xff0c;这可能不是我们期望的&#xff0c;因此我们会碰到需要删除文本文件中重复行的情况。如果是人工删除&#xff0c;当文件较大或者数量较多的时候&#xff0c;处理的难度会较大。今天就给大家介绍一下批量删除文本…...

LangChain 使用向量数据库介绍与使用

LangChain 是一个用于构建大语言模型(LLM)应用的框架,而向量数据库在 LangChain 中主要用于实现检索增强生成(RAG, Retrieval-Augmented Generation),即通过向量搜索从外部知识库中快速检索相关信息,辅助大模型生成更准确的回答。以下是具体的使用方法: 1. 核心流程 L…...