C++:vector增删查改模拟实现

news2024/11/25 18:54:30

C++:vector增删查改模拟实现

  • 前言
  • 一、迭代器
    • 1.1 非const迭代器:begin()、end()
    • 1.2 const迭代器:begin()、end()
  • 二、构造函数、拷贝构造函数、赋值重载、析构函数模拟实现
    • 2.1 构造函数
      • 2.1.1 无参构造
      • 2.1.2 迭代器区间构造
      • 2.1.3 n个值构造
    • 2.2 拷贝构造
    • 2.3 赋值重载
    • 3 析构函数
  • 三、容量相关:capacity()、size()、reserve()、resize()
    • 3.1 capacity()
    • 3.2 size()
    • 3.3 reserve()
    • 3.4 resize()
  • 四、operator[ ]重载
  • 五、元素相关:insert、erase、push_back、pop_back
    • 5.1 insert()
    • 5.2 erase()
      • 5.2.1 erase迭代器失效
    • 5.3 push_bach()
    • 5.4 pop_back()
  • 六、所有代码


前言

提前在这说明下,vector增删查改模拟实现的成员变量博主采用SGI版本。下面给出其库中成员变量是哪些,后续的模拟实现都基于此。

在这里插入图片描述
我们发现库中定义了3个T*的变量。同时3个成员变量的意义如下:

在这里插入图片描述

一、迭代器

1.1 非const迭代器:begin()、end()

typedef T* iterator;

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

1.2 const迭代器:begin()、end()

typedef const T* const_iterator;

const_iterator begin()const
{
	return _start;
}

const_iterator end()const
{
	return _finish;
}

二、构造函数、拷贝构造函数、赋值重载、析构函数模拟实现

2.1 构造函数

我们先来看看vector库中的构造类型如下:
在这里插入图片描述
我们知道有三种构造方式,下面给出各种的实现方式。

2.1.1 无参构造

vector()
		:_start(nullptr)
		,_finish(nullptr)
		, _endofstorage
	{}

2.1.2 迭代器区间构造

template<class InputIterator>//使用模板是为了,当数据类型匹配时就可以使用
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

2.1.3 n个值构造

vector(size_t  n, const T& value = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(value);
	}
}

//防止定义vector<int>这种类型走迭代区间的构造函数,我们在多实现一个以下类型函数
//当使用vector<int>类型的去构造时,此时调用的构造函数两个参数都是int。所有他会走最匹配的函数,即迭代器区间构造生成的构造函数,程序会出错。
//而下面给出了现成的最匹配构造函数,编译器调用时就不会走模板。
vector(int n, const T& value = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(value);
	}
}

2.2 拷贝构造

拷贝构造我们先调用开好一块空间,在依次插入数据即可。

//vector(const vector& x) //库中实现模式, 直接使用类名。但C++,类型不是类名。
//这里各位读者了解下这里直接类名做类型也正确即可,但不建议各位这样做。
vector(const vector<T>& v)
{
	reserve(v.capacity());//后面会给出实现
	for (auto& e : v)
	{
		push_back(e);
	}
}

2.3 赋值重载

赋值重载这里我们不要传引用,而是直接传参即可。编译器调用拷贝构造生成形参后,在调用swap()函数依次交换形参和this即可。

//赋值重载
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v_endofstorage);
}

vector<T>& operator= (vector<T> tmp)
{
	swap(tmp);
	return *this;
}		

3 析构函数

~vector()
{
	delete[] _start;
	_start = _finish = _endofstorage = nullptr;
}

三、容量相关:capacity()、size()、reserve()、resize()

3.1 capacity()

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

3.2 size()

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

3.3 reserve()

由于stl中我们一般不缩容,所以先判断reserve的空间大小是否比当前空间容量大。
如果reserve的空间更大,所以我们需要先开好目标大小的空间,在将原数据拷贝过去,最后析构原来空间即可。
但下面这两种实现方式对吗?

第一种:

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)//如果原来空间有数据,拷贝到新空间
		{
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;
		}
		//更新_start、_finish、_endofstorage。指向新空间中相应位置
		_start = tmp;
		_finish = _start + size();
		_endofstorage = _start + n;
	}
}

先说结论:上诉这段代码是错的。
在我们调试后会发现_finish的值没有更新。(这里大家自行验证下接口)

原因:(win11画图一直很模糊,博主也很无奈,各位将就看吧)
在这里插入图片描述


第二种:
为了解决上诉问题,我们可以先记录_finish和_start的偏移量,用来代替size()函数。
所以初学者很容易写出以下代码:

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();//记录_finish 和 _start 的偏移量
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * sz);
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + sz;//不能用size()代替sz,否则会导致迭代器失效
		_endofstorage = _start + n;
	}
}

那这是否正确呢?答案是否定的。
我们来看看下面这种场景:
在这里插入图片描述


实际上对于这种情况,可以自己循环依次赋值即可。内置类型直接拷贝数据;内置类型调用赋值重载,是一种深拷贝。

最终代码如下:

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();//记录_finish 和 _start 的偏移量
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * sz);
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + sz;//不能用size()代替sz,否则会导致迭代器失效
		_endofstorage = _start + n;
	}
}

3.4 resize()

resize逻辑还是很简单的。
首先判断resize()的目标大小n和有效数据个数size()谁大。如果有效个数size()更大,只需更改_finish即可;否则要先进行扩容(reserve会将原有数据拷贝到新空间),然后从_finish开始向扩充的空间插入新的值。

代码如下:

//const会延长匿名对象的生命周期, 匿名对象具有常性
//模板出来后,对类进行了升级,内置类型也有构造函数
//void resize(size_t n, T val = T())
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++;
		}
	}
}

四、operator[ ]重载

T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}

const T& operator[](size_t pos)const
{
	assert(pos < _finish);
	return _start[pos];
}

五、元素相关:insert、erase、push_back、pop_back

5.1 insert()

任意位置插入数据,首先需判断是否需要扩容。然后将插入位置pos开始往后的数据向后移动,最后将新数据插入到pos处即可。
tips:

  • 如果发生扩容,需要先记录pos和_start之间的偏移量。在将pos位置跟新,指向新空间中对应位置。否则会导致迭代器失效
void insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _endofstroage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}

	//插入数据
	*pos = x;
	_finish++;
}

5.2 erase()

任意位置删除数据,只需要从pos+1开始,将后续数据全部依次向前移动覆盖,最后更新_finish即可。

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;

	return pos;
}

5.2.1 erase迭代器失效

void testvector4()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	auto it = v.begin();
	while (it < v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
		}
			it++;
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

上述代码本意是将偶数全部删除,结果本该是1 、5。但结果却是:
在这里插入图片描述
为什么呢?
这是因为我们删除元素后,后续数据会补上空缺。所以当使用erase后,迭代器会失效。(上述结果是g++的实现机制,在vs2019下上述代码会直接报错。原因在于vs2019对erase后的空间做强制检查,不允许访问)。为此stl库给出的解决方案是接受删除位置的下一个元素的返回值。(这也是为什么整个模拟实现中只有erase函数具有返回值),并接收返回值。

正确删除偶数方法:

void testvector4()
	{
		//std::vector<int> v;
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(2);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);
		auto it = v.begin();
		//迭代器失效
		/*while (it < v.end())
		{
			if (*it % 2 == 0)
			{
				v.erase(it);
			}
				it++;
		}*/

		while (it < v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.erase(it);
			}
			else
			{
				it++;
			}
		}

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

5.3 push_bach()

头插复用insert函数即可。

void push_back(const T& x)
{
	//if (_finish == _endofstroage)
	//{
	//	reserve(capacity() == 0 ? 4 : capacity() * 2);
	//}

	插入数据
	//*_finish = x;
	//_finish++;

	insert(_finish, x);
}

5.4 pop_back()

复用erase,尾删

void pop_back()
{
	erase(--end());
}

六、所有代码

vector增删查改模拟实现gitee链接

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【无标将列表中的多组参数依次带入指定的函数将每次调用函数返回结果组成列表itertools.starmap()题】

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将列表中的多组参数 依次带入指定的函数 将每次调用函数 返回结果组成列表 itertools.starmap() [太阳]选择题 请问以下代码输出的结果是&#xff1f; import itertools a [(1, 2), (3, 4)] p…

leetcode系列:反转链表的形象表示

反转链表是一道比较简单的题&#xff0c;主要考察的是对链表数据结构的理解和双指针应用&#xff0c;比较容易出错的地方是指针的移动顺序。在练习的过程中想到了一个比较形象的表示方法&#xff0c;于是记录下来。 # Definition for singly-linked list. # class ListNode: #…

MATLAB - 凸优化(Convex Optimization)

系列文章目录 前言 凸优化&#xff08;Convex optimization&#xff09;是在凸约束&#xff08;convex constraints&#xff09;条件下使凸目标函数&#xff08;convex objective function&#xff09;最小化的过程&#xff0c;或者等同于在凸约束条件下使凹目标函数最大化的过…

四元数,欧拉角,旋转矩阵,旋转向量

四元数&#xff0c;旋转矩阵&#xff0c;旋转向量&#xff0c;欧拉角 一、欧拉角 1、欧拉角是表达旋转的最简单的一种方式&#xff0c;形式上它是一个三维向量&#xff0c;其值分别代表物体绕坐标系三个轴(x,y,z轴&#xff09;的旋转角度&#xff0c;默认旋转正向为逆坐标轴逆…

华为OD机试 - 生成哈夫曼树(Java JS Python C)

题目描述 给定长度为 n 的无序的数字数组,每个数字代表二叉树的叶子节点的权值,数字数组的值均大于等于1。 请完成一个函数,根据输入的数字数组,生成哈夫曼树,并将哈夫曼树按照中序遍历输出。 为了保证输出的二叉树中序遍历结果统一,增加以下限制: 二叉树节点中,左节…

mybatis和mybatisplus中对 同namespace 中id重复处理逻辑源码解析

一、背景 同事在同一个mapper.xml &#xff08;namespace相同&#xff09;&#xff0c;复制了一个sql没有修改id&#xff0c;正常启动项目。但是我以前使用mybatis的时候如果在namespace相同情况下&#xff0c;id重复&#xff0c;项目会报错无法正常启动&#xff0c;后来看代码…

Vue3-01-创建项目

环境准备 1.需要用到 16.0 以及更高版本的 node.js 2.使用vscode编辑器进行项目开发可以在命令行中查看node的版本号: node -v创建项目 1.准备一个目录 例如&#xff0c;我创建项目的时候是在该目录下进行的;D:\projectsTest\vue3project2.执行创建命令&#xff08;*&#x…

React聚焦渲染速度

目录 一、引言 二、React.js的渲染速度机制 虚拟DOM Diff算法 三、优化React.js的渲染速度 避免不必要的重新渲染 使用合适的数据结构和算法 使用React Profiler工具进行性能分析 四、实际案例分析 五、总结 一、引言 在当今的Web开发领域&#xff0c;React.js无疑是…

屏蔽百度首页推荐和热搜的实战方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

第二十一章

计算机应用实现了多台计算机间的互联&#xff0c;使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序&#xff0c;这些程序借助于网络协议&#xff0c;相互之间可以交换数据。编写网络应用程序前&#xff0c;首先必须明确所要使用的网络协议…

C++STL的string(超详解)

文章目录 前言C语言的字符串 stringstring类的常用接口string类的常见构造string (const string& str);string (const string& str, size_t pos, size_t len npos); capacitysize和lengthreserveresizeresize可以删除数据 modify尾插插入字符插入字符串 inserterasere…

软件设计中如何画各类图之八深入解析部署图:物理布局与系统架构的视觉化呈现

目录 1 前言2 部署图的符号及说明3 画部署图的步骤3.1 **识别节点**3.2 **定义组件**3.3 **标识部署关系**3.4 **添加细节** 4 部署图的用途4.1 **系统设计与规划**4.2 **系统架构分析**4.3 **系统维护与升级** 5 实际场景举例5.1 Web应用部署图5.2 云端服务部署图 6 结语 1 前…

尝试通过AI模型进行简单的编码

一、前言 最近尝试通过AI来编程&#xff0c;总体感觉还是能处理写简单的问题&#xff0c;复杂的问题目前还是无法解决。主要的痛点还是数据噪音&#xff0c;就是AI永远不会承认它不会&#xff0c;它会给你的一个错误的信息&#xff0c;它也不会告诉你你的问题它暂时无法完整正…

mac苹果笔记本电脑如何强力删除卸载app软件?

苹果电脑怎样删除app&#xff1f;不是把app移到废纸篓就行了吗&#xff0c;十分简单呢&#xff01; 其实不然&#xff0c;因为在Mac电脑上&#xff0c;删除应用程序只是删除了应用程序的主要组件。大多数时候&#xff0c;系统会有一个相当长的目录&#xff0c;包含所有与应用程…

SCI一区级 | Matlab实现GWO-CNN-BiLSTM-selfAttention多变量多步时间序列预测

SCI一区级 | Matlab实现GWO-CNN-BiLSTM-selfAttention多变量多步时间序列预测 目录 SCI一区级 | Matlab实现GWO-CNN-BiLSTM-selfAttention多变量多步时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-CNN-BiLSTM-selfAttention灰狼算法优化卷…

通过误差改变控制的两种策略

如果反馈误差越来越大&#xff0c;需要改变调节方向以减小误差并实现更好的控制。以下是两种常见的调节方向改变的方法&#xff1a; PID控制器中的积分限制&#xff1a;在PID控制中&#xff0c;积分项可以用来减小稳态误差。然而&#xff0c;当反馈误差持续增大时&#xff0c;积…

java打包到docker,以及idea远程调试

这里主要介绍 dockerfile的打包方式 一、打包jar包到容器 1. 在要打包的项目中创建dockerfile&#xff0c;dockerfile与项目的pom.xml是同级 2. 编辑dockerfile文件 FROM openjdk:8 VOLUME ["/data/untitled"] COPY target/untitled-1.0.jar "/app.jar"…

Spring Boot 整合 xxl-job 保姆级教程!

文章目录 介绍使用初始化“调度数据库”配置调度中心配置“执行器项目”调度任务 介绍 首先我们介绍一下什么是xxl-job&#xff0c;根据官方定义&#xff0c;XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码…

Kubersphere应用【二】Docker安装

一、Docker安装 1.下载Docker安装包 【地址】Index of linux/static/stable/x86_64/ 2.上传至服务器 # 解压文件 tar -xvf docker-20.10.10.tgz# 将docker 目录中的所有文件复制至/usr/bin/目录下 cp docker/* /usr/bin 3.配置docker.service文件 vim /usr/lib/systemd/sy…

树莓派 5 - Raspberry Pi 5 入门教程

系列文章目录 文章目录 ​​​​​​​ 前言 如果您是第一次使用 Raspberry Pi&#xff0c;请参阅我们的入门指南&#xff08;how to get started&#xff09;。 Raspberry Pi 5 Raspberry Pi 5 配备了运行频率为 2.4GHz 的 64 位四核 Arm Cortex-A76 处理器&#xff0c;CPU 性…