【C++标准模版库】模拟实现vector+迭代器失效问题

模拟实现vector

  • 一.vector成员变量
  • 二.构造函数
    • 1.无参(默认)构造
    • 2.有参构造
    • 3.拷贝构造
      • 1.传统写法
      • 2.现代写法
  • 三.vector对象的容量操作
    • 1.size
    • 2.capacity
    • 3.clear
    • 4.empty
    • 5.reserve
    • 6.resize
  • 四.vector对象的访问及遍历操作
    • 1.operator[]
    • 2.实现迭代器:begin+end
  • 五.vector对象的增删查改操作
    • 1.operator
      • 1.传统写法
      • 2.现代写法
    • 2.push_back
    • 3.pop_back
    • 4.insert
      • 迭代器失效:类似野指针
      • 迭代器失效:位置意义改变
    • 5.erase
  • 六.源代码
    • 1.vector.h

一.vector成员变量

成员变量:

  1. iterator _start:指向vector的起始位置。
  2. iterator _finish:指向vector的最后一个有效数据的下一个位置。
  3. iterator _end_of_storage:指向vector可容纳最大数据个数的下一个位置。

大体结构如下:

在这里插入图片描述

namespace xzy
{//有模版不能分离到.h与.cpp文件,否则报链接错误template<class T>class vector{public:typedef T* iterator;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

二.构造函数

1.无参(默认)构造

类内函数拷贝构造,系统就不再提供默认构造,无法vector<int> v; 需要自己提供默认构造,C++11中vector() = default; 强制生成默认构造。

//C++11 强制生成默认构造
//vector() = default;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{}

2.有参构造

  1. 迭代器区间构造:例如用list对象的迭代器区间构造vector对象。作用:例如由于list对象排序性能较低,利用vector的迭代器区间初始化list对象中相同的数据进行排序,可以提高性能。
//类模版的成员函数可以是函数模版
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
int main()
{list<int> l1(10, 1); //数据类型要求匹配,例如:都是intvector<int> v1(l1.begin(), l1.end());
}
  1. n个值为val初始化:
vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

在这里插入图片描述

解决办法:

在这里插入图片描述

3.拷贝构造

1.传统写法

vector(const vector<T>& v)
{_start = new T[v.capacity()];//memmove(_start, v._start, v.size() * sizeof(T));for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}

更好的写法:遍历vector进行尾插。

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

2.现代写法

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(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());swap(tmp);
}

三.vector对象的容量操作

1.size

size_t size() const
{return _finish - _start;
}

2.capacity

size_t capacity() const
{return _end_of_storage - _start;
}

3.clear

void clear()
{_finish = _start;
}

4.empty

bool empty() const
{return _start == _finish;
}

5.reserve

扩容时:先开辟新空间,再将旧空间拷贝到空间,释放旧空间,最后修改数据。

但是这里存在一个坑,如下:

在这里插入图片描述

初始_start 和 _finish 为缺省值 nullptr。
_finish = _start + size() = _start + _finish - _start = _finish = nullptr——>程序崩溃。

正确代码如下:

void reserve(size_t n)
{//第一种解决方法//if (n > capacity())//{//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_finish = tmp + size();//	_start = tmp;//	_end_of_storage = _start + n;//}//第二种解决方法//if (n > capacity())//{//	size_t old_size = size();//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_start = tmp;//	_finish = _start + old_size;//	_end_of_storage = _start + 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;_end_of_storage = _start + n;}
}

但是第一种与第二种方法在某些vector容器数据为自定义类型时存在浅拷贝问题如下图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同理:拷贝构造使用的memmove也存在此问题。

6.resize

修改有效数据的个数时:若传入的参数小于有效数据的个数:删除数据即修改_finish即可;若传入的参数大于有效数据的个数:插入数据前考虑是否扩容。

void resize(size_t n, const T& val = T())
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}
}

在这里插入图片描述

四.vector对象的访问及遍历操作

1.operator[]

T& operator[](size_t i)
{assert(i >= 0 && i < size());return _start[i];
}const T& operator[](size_t i) const
{assert(i >= 0 && i < size());return _start[i];
}

2.实现迭代器:begin+end

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;
}

五.vector对象的增删查改操作

1.operator

1.传统写法

vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[] _start;_start = new T[v.capacity()];//memcpy(_start, v._start, v.size() * sizeof(T)); //当vector<string>时存在问题for(size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}

更好的写法:遍历vector进行尾插。

vector<T>& operator=(const vector<T>& v)
{if (this != &v){clear();reserve(v.size());for (auto& e : v){push_back(e);}}return *this;
}

2.现代写法

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& operator=(vector tmp)
vector<T>& operator=(vector<T> tmp)
{swap(tmp);return *this;
}

2.push_back

尾插时:先检查容量,再进行尾插。

void push_back(const T& x)
{//容量满了——>扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//尾插*_finish = x;++_finish;
}

3.pop_back

尾删时:先检查是否有有效数据,再进行尾删。

void pop_back()
{assert(!empty());--_finish;
}

4.insert

插入时:先检查容量,再整体右移一位,最后插入。

迭代器失效:类似野指针

实现插入操作有一个坑造成迭代器失效问题,如下图:

在这里插入图片描述

修改后的代码能解决上面的问题,但是又存在另一个迭代器失效的问题,如下图:

在这里插入图片描述

迭代器失效:位置意义改变

在这里插入图片描述

由于不知道insert函数内是否存在扩容,不能确保pos是否为野指针,解决方法:可以在insert函数中返回pos用于接受。代码如下:

iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);//容量满了——>扩容if (_finish == _end_of_storage){//记录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;
}

5.erase

同理erase也会遇到的迭代器失效:位置意义改变,在vs2022中的vector(SLT)不接收erase的返回值强行访问,程序崩溃。接收erase的返回值访问,程序正常运行。

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中的偶数:

int main()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);}else{it++;}}return 0;
}

六.源代码

1.vector.h

//#pragma once#ifndef __VECTOR_H__
#define __VECTOR_H__#include<iostream>
#include<assert.h>
using namespace std;namespace xzy
{//有模版不能分离到.h与.cpp文件,否则报链接错误template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//C++11 强制生成默认构造//vector() = default;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(const vector<T>& v){_start = new T[v.capacity()];//memmove(_start, v._start, v.size() * sizeof(T)); //当vector<string>时存在问题for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}/*vector(const vector<T>& v){reserve(v.size());for (auto& e : v){push_back(e);}}*///类模版的成员函数可以是函数模版template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}void clear(){_finish = _start;}//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		delete[] _start;//		_start = new T[v.capacity()];//		//memcpy(_start, v._start, v.size() * sizeof(T));//当vector<string>时存在问题//      for(size_t i = 0; i < v.size(); i++)//        {//			  _start[i] = v._start[i];//        }//		_finish = _start + v.size();//		_end_of_storage = _start + v.capacity();//	}//	return *this;//}//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		clear();//		reserve(v.size());//		for (auto& e : v)//		{//			push_back(e);//		}//	}//	return *this;//}void swap(vector<int>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}类内可以用类名替代类型:vector& operator=(vector tmp)vector<T>& operator=(vector<T> tmp){swap(tmp);return *this;}~vector(){if (_start != nullptr){delete[] _start;}_start = _finish = _end_of_storage;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//以下有一个坑//void reserve(size_t n)//{//	if (n > capacity())//	{//		//开空间//		T* tmp = new T[n];//		//拷贝数据//		memmove(tmp, _start, sizeof(T) * size());//		//释放旧空间//		delete[] _start;//		//更新数据//		_start = tmp;//		_finish = _start + size(); 注意:size()已经不再是有效数据个数了,可以通过调试观察//		_end_of_storage = _start + n;//	}//}void reserve(size_t n){//第一种解决方法:依旧存在错误//if (n > capacity())//{//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_finish = tmp + size();//	_start = tmp;//	_end_of_storage = _start + n;//}//第二种解决方法:依旧存在错误/*if (n > capacity()){size_t old_size = size();T* tmp = new T[n];memmove(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + 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;_end_of_storage = _start + n;}}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void push_back(const T& x){//容量满了——>扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//尾插*_finish = x;++_finish;}bool empty(){return _start == _finish;}void pop_back(){assert(!empty());--_finish;}//void insert(iterator pos, const T& x)//{//	//容量满了——>扩容//	if (_finish == _end_of_storage)//	{//		reserve(capacity() == 0 ? 4 : 2 * capacity());//	}//	//整体后移一位//	iterator end = _finish;//	while (end > pos)//	{//		*end = *(end - 1);//		--end;//	}//	//插入数据//	*pos = x; //pos为野指针,迭代器失效//	++_finish;//}iterator insert(iterator pos, const T& x){//容量满了——>扩容if (_finish == _end_of_storage){//记录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;}iterator erase(iterator pos){iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;}T& operator[](size_t i){assert(i >= 0 && i < size());return _start[i];}const T& operator[](size_t i) const{assert(i >= 0 && i < size());return _start[i];}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/390375.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费【2024】springboot 大学生志愿者管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

windows下设置java环境变量

1.打开window的环境变量设置 右键开始菜单选择系统 选择高级系统设置&#xff1a; 点击环境变量 2.在系统变量 新增 JAVA_HOME&#xff1b;该变量的值 选择jdk所在的目录即可。 JAVA_HOME: D:\Program Files\Java\jdk1.8.0_131 3. 在系统变量新增 classpath; 该变量的值设置…

GoLang 安装

golang学习笔记 goland 安装 To use Go programming language in Visual Studio Code (VSCode), you can follow these steps: 1. Install Go: Download and install the latest version of Go from the official Go website (https://golang.org/dl/). 2. Install VSCode:…

llama-3.1下载部署

llama-3.1 下载部署 下载 huggingface 详情页填写申请后等待审核 点击 头像->setting->access token 创建token 配置环境变量 下载模型 pip install -U huggingface_hubhuggingface-cli download --resume-download meta-llama/Meta-Llama-3.1-8B-Instruct --loca…

IDEA的疑难杂症

注意idea版本是否与maven版本兼容 2019idea与maven3.6以上不兼容 IDEA无法启动 打开idea下载安装的目录:如:Idea\IntelliJ IDEA 2024.1\bin 在bin下面找到 打开在最后一行添加暂停 pause 之后双击运行idea.bat 提示找不到一个jar包,切记不要有中文目录 IDEA缓存 …

Java与Python谁更适合后端开发?

在软件开发的世界里&#xff0c;选择合适的编程语言就像为建筑选择合适的材料一样重要。 对于后端开发而言&#xff0c;Java和Python都是流行的选择&#xff0c;但它们各自拥有独特的优势和劣势&#xff0c;“谁更适合”就成为一个被议论的话题。 事实上&#xff0c;并不存在…

每日学术速递8.2

1.A Scalable Quantum Non-local Neural Network for Image Classification 标题&#xff1a; 用于图像分类的可扩展量子非局部神经网络 作者&#xff1a; Sparsh Gupta, Debanjan Konar, Vaneet Aggarwal 文章链接&#xff1a;https://arxiv.org/abs/2407.18906 摘要&#x…

[BJDCTF2020]Easy MD51

抓包看一下信息&#xff0c;发现有sql注入字段 输入 注入发现 查看源码 然后get传参?aQNKCDZO&bs214587387a 最后 MD5函数的弱类型比较 发现PHP代码&#xff0c;分析仍为 PHP md5绕过。 使用数组绕过POST传入param1[]1&param2[]2&#xff0c;得到flag。

RIP综合练习

要求&#xff1a; 1.合理使用IP地址划分网络&#xff0c;各自创建循环接口 2.R1创建环回172.16.1.1/24 172.16.2.1/24 172.16.3.1/24 3.要求R3使用R2访问R1环回 4.减少路由条目数量&#xff0c;R1,R2之间增加路由传递安全性 5.R5创建一个环回模拟运营商&#xff0c;不能…

打卡第31天------贪心算法

每天抓紧时间刷题,争取尽快上岸,不能再耽误一分一秒了,2024年已经过去大半年了。这个算法编程题是我的痛点。要尽快弥补。 卡尔在讲算法题的时候,思路比较清晰,通俗易懂,以前看见算法题就害怕,因为啥都不会,看懵了,跟了一个月了,每天坚持刷题,偶尔会回顾思路,也会…

开源Spring Boot版本WebSSH:轻松在浏览器中管理SSH和FTP

介绍 WebSSH 是一个轻量级的开源ssh工具&#xff0c;只需安装在服务端&#xff0c;就可以通过浏览器访问SSH和FTP。它支持文件和日志高亮显示&#xff0c;Vim 和 Top 命令&#xff0c;实时查看日志&#xff0c;并且操作体验与标准的 Shell 基本相同。WebSSH 支持多会话、文件上…

【Git】git 从入门到实战系列(二)—— git 介绍以及安装方法 (文末附带视频录制操作步骤)

文章目录 一、前言二、git 是什么三、版本控制系统是什么四、本地 vs 集中式 vs 分布式本地版本控制系统集中式版本控制系统分布式版本控制系统 五、安装 git 一、前言 本系列上一篇文章【Git】git 从入门到实战系列&#xff08;一&#xff09;—— Git 的诞生&#xff0c;Lin…

【2024蓝桥杯/C++/B组/小球反弹】

题目 分析 Sx 2 * k1 * x; Sy 2 * k2 * y; &#xff08;其中k1, k2为整数&#xff09; Vx * t Sx; Vy * t Sy; k1 / k2 (15 * y) / (17 * x)&#xff1b; 目标1&#xff1a;根据k1与k2的关系&#xff0c;找出一组最小整数组&#xff08;k1, k2&#xff09;&#xff…

NLP-使用Word2vec实现文本分类

Word2Vec模型通过学习大量文本数据&#xff0c;将每个单词表示为一个连续的向量&#xff0c;这些向量可以捕捉单词之间的语义和句法关系。本文做文本分类是结合Word2Vec文本内容text&#xff0c;预测其文本标签label。以下使用mock商品数据的代码实现过程过下&#xff1a; 1、…

PCL从理解到应用【08】 点云特征 | 法线估计 | 主曲率估计

前言 在PCL中&#xff0c;有多种方法和函数可以用来提取点云特征&#xff0c;本文介绍几何特征。 其中&#xff0c;几何特征主要包括法线估计和主曲率估计。 这些特征能够描述点云表面的几何形状&#xff0c;常用于进一步的点云处理和分析&#xff0c;如配准、分割和物体识别…

利用canvas 实现图片的标注,把标注像素点传入到后端

背景&#xff1a;我们有一个摄像的产品&#xff0c;拍照传统的水表盘面&#xff0c;我们需要框选水表读数&#xff0c;标注点传到后端&#xff0c;后端根据标注点自动去截取摄像表拍摄回来的图片&#xff0c;然后拿到大模型里面进行训练。由于同一只表拍摄的画面都是一样的&…

【时时三省】unity test 测试框架 使用 code blocks 移植

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 目录 1&#xff0c;使用 Code::Blocks 17.12 创建工程 2&#xff0c;移植文件至该工程下&#xff1a; 移入的文件为: 被移入的文件介绍&#xff1a; 更改代码&#xff1a; 向工程添加文…

k8s 部署RuoYi-Vue-Plus之ingress域名解析

可参看https://blog.csdn.net/weimeibuqieryu/article/details/140798925 搭建ingress 1.创建Ingress对象 ingress-ruoyi.yaml其中host替换为你对应域名&#xff0c;需要解析域名到服务器, 同时为后端服务添加了二级域名解析 api. 访问http://xxx.xyz/就能访问前端&#xff0…

力扣SQL50 修复表中的名字 字符串函数

Problem: 1667. 修复表中的名字 &#x1f468;‍&#x1f3eb; 参考题解 select user_id, CONCAT(UPPER(left(name, 1)), LOWER(RIGHT(name, length(name) - 1))) as name from Users order by user_id

【Linux系统编程】:进程地址空间1

1.引出进程地址空间 因为str指向的是字符串首字母的地址&#xff0c;首字母是字符常量“h”&#xff0c;地址存储在字符常量区&#xff0c;无法修改&#xff0c;故报错。 Linux进程地址空间与进程内存布局详解 - 知乎 (zhihu.com) 我们编写一段代码&#xff0c;来认识一下存储…