19. C++STL 5(深入理解vector,vector的模拟实现,二维动态数组)

⭐本篇重点:vector深入理解和模拟实现

⭐本篇代码:c++学习/09.vector-2 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

目录

一. 深入理解vector

二. 使用模板模拟实现vector (包含迭代器)

2.1 模拟vector类的成员

2.1 size capacity

2.3 构造函数与拷贝构造函数

2.4 析构函数

2.5 reverse与resize

2.6 Insert与push_back 

2.7 测试代码1

​编辑 2.8 erase和pop_back

2.9 测试代码2

2.10 迭代器begin和end

2.11 测试代码3

三. 测试类类型的变量

四. vector完成动态二维数组 


一. 深入理解vector

        上篇vector我们提到,当vector插入元素之后,如果空间不足就会扩容1.5倍或者2倍。如果扩容之后空间仍然不够会再次扩容。

        并且vector的扩容不是在原空间上直接扩容,而是新开辟一份空间,拷贝原空间的数据到新空间,放入新元素,释放空间等过程。

        所以我们使用vector之前,要先预算并开辟要使用的空间。避免频繁扩容导致效率降低。最好不要频繁的使用push_back

二. 使用模板模拟实现vector (包含迭代器)

源文件 test.cpp用于测试

头文件 myVector.h用于声明和定义vector类

2.1 模拟vector类的成员

        根据我们使用的vector,可以知道vector至少有这些成员变量

#pragma once
#include <iostream>
using  namespace std;

namespace YZC
{
	template<class T>
	class myVector
	{
	public:

	private:
		T* _begin;	      //数组开始
		T* _end;	      //数组结尾
		T* _endOfStorage; //容量结尾
	};
}

_begin表示这个容器开始的位置,_end表示结尾位置。_endOfStorage表示容量结尾的位置

为了支持迭代器,我们可以进一步封装 

#pragma once
#include <iostream>
using  namespace std;

namespace YZC
{
	template<class T>
	class myVector
	{
		//添加迭代器
		typedef T* iterator;
		typedef const T* const_iterator;
	public:

	private:
		iterator _begin;	    //数组开始
		iterator _end;	        //数组结尾
		iterator _endOfStorage; //容量结尾
	};
}

 而成员函数包括:

        构造函数,析构函数,拷贝构造,赋值运算符重载,扩容reverse,设置元素resize,插入元素push_back,insert,删除元素pop_back,erase等...

        以及 size,capacity,begin,end,[]重载,等访问修改元素,容量,迭代器等函数

2.1 size capacity

        这两个函数比较简单,直接通过迭代器相减即可获得

		//获取容量
		size_t capacity()
		{
			return _endOfStorage - _begin;
		}

		//[]重载
		T& operator[](size_t i)
		{
			//注意要防止数组越界
			assert(i < size());
			return _begin[i];
		}

		//[]重载
		const T& operator[](size_t i)const
		{
			assert(i < size());
			return _begin[i];
		}

2.3 构造函数与拷贝构造函数

        主要拷贝构造函数需要进行深拷贝

#pragma once
#include <iostream>
using  namespace std;

namespace YZC
{
	template<class T>
	class myVector
	{
		//添加迭代器
		typedef T* iterator;
		typedef const T* const_iterator;
	public:
		//构造函数
		myVector()
			:_begin(nullptr)
			, _end(nullptr)
			, _endOfStorage(nullptr)
		{};

		//拷贝构造
		myVector(const myVector& v)
		{
			//需要深拷贝
			//依次拷贝成员变量,然后赋值
			_begin = new T[v.capacity()];
			_end = _begin;	//end在拷贝元素之后计算获取
			_endOfStorage = _begin + v.capacity();

			for (size_t i = 0; i < v.size(); i++)
			{
				*_end = v[i];
				_end++;
			}
		}

		//获取元素数量
		size_t size()
		{
			return _end - _begin;
		}

		//获取容量
		size_t capacity()
		{
			return _endOfStorage - _begin;
		}

		//[]重载
		T& operator[](size_t i)
		{
			//注意要防止数组越界
			assert(i < size());
			return _begin[i];
		}

		//[]重载
		const T& operator[](size_t i)const
		{
			assert(i < size());
			return _begin[i];
		}
	private:
		iterator _begin;	    //数组开始
		iterator _end;	        //数组结尾
		iterator _endOfStorage; //容量结尾
	};
}

2.4 析构函数


		~myVector()
		{
			delete[] _begin;
			_begin = _end = _endOfStorage = nullptr;
		}

2.5 reverse与resize

        注意好扩容的机制即可,开辟新空间,转移元素,销毁旧空间。

注意拷贝数据的时候,要深拷贝,不能简单的使用memcpy进行浅拷贝

		void reserve(size_t n)
		{
			//防止非法扩容
			assert(n >= 0);
			//开始扩容
			if (n > capacity())
			{
				//开辟新空间
				size_t oldsize = size();	//旧空间元素的大小
				T* newbegin = new T[n];
				if (_begin != nullptr)
				{
					//拷贝旧元素
					//不可以使用memcpy,因为这是浅拷贝
					for (size_t i = 0; i < oldsize; i++)
					{
						newbegin[i] = _begin[i];	//对于类类型,调用其operator= 是深拷贝
					}
					//删除旧空间
					delete[] _begin;
					_begin = nullptr;
				}
				_begin = newbegin;
				_end = _begin + oldsize;
				_endOfStorage = _begin + n;
			}
		}

		void resize(size_t n, const T& val = T())	//T()表示这种类型的默认值,比如int()=0
		{
			if (n <= size())
			{
				_end = _begin + n;
			}
			else
			{
				//如果n大于容量,调用reverse进行扩容
				if (n > capacity())
				{
					reserve(n);
				}
				//赋值val
				while (_end < _begin+ n)
				{
					*_end = val;
					_end++;
				}
			}
		}

2.6 Insert与push_back 

        对于push_back我们可以复用Insert的代码,直接在结尾插入。所以我们主要实现Insert这个函数的代码。

Insert

		//在pos位置插入元素x
		void Insert(iterator pos, const T& x)
		{
			//1. 需要合法插入
			assert(pos <= _end);	//当pos和end相等的时候就是尾插push_back
			//2. 判断是否需要扩容
			if (_end == _endOfStorage)
			{
				//扩容导致迭代器失效,需要提前计算好pos的位置
				size_t newpos = pos - _begin;
				size_t newcapacity = capacity() == 0 ? 3 : capacity() * 2;
				reserve(newcapacity);
				pos = _begin + newpos; //重新设置pos
			}

			//插入元素
			iterator end = _end - 1;
			while (end >= pos)    //不可end != pos    假如只有一个数据就会崩溃
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_end++;
		}

push_back

复用Insert的代码

		//尾插
		void push_back(const T& x)
		{
			Insert(_end, x);
		}

2.7 测试代码1

        我们的代码已经基本完善了,可以进行一次测试验证是否有bug

头文件 myVector.h 如下 

#pragma once
#include <iostream>
#include <cassert>
using  namespace std;

namespace YZC
{
	template<class T>
	class myVector
	{
		//添加迭代器
		typedef T* iterator;
		typedef const T* const_iterator;
	public:
		//构造函数
		myVector()
			:_begin(nullptr)
			, _end(nullptr)
			, _endOfStorage(nullptr)
		{};

		//拷贝构造
		myVector(const myVector& v)
		{
			//需要深拷贝
			//依次拷贝成员变量,然后赋值
			_begin = new T[v.capacity()];
			_end = _begin;	//end在拷贝元素之后计算获取
			_endOfStorage = _begin + v.capacity();

			for (size_t i = 0; i < v.size(); i++)
			{
				*_end = v[i];
				_end++;
			}
		}

		~myVector()
		{
			delete[] _begin;
			_begin = _end = _endOfStorage = nullptr;
		}

		void reserve(size_t n)
		{
			//防止非法扩容
			assert(n >= 0);
			//开始扩容
			if (n > capacity())
			{
				//开辟新空间
				size_t oldsize = size();	//旧空间元素的大小
				T* newbegin = new T[n];
				if (_begin != nullptr)
				{
					//拷贝旧元素
					//不可以使用memcpy,因为这是浅拷贝
					for (size_t i = 0; i < oldsize; i++)
					{
						newbegin[i] = _begin[i];	//对于类类型,调用其operator= 是深拷贝
					}
					//删除旧空间
					delete[] _begin;
					_begin = nullptr;
				}
				_begin = newbegin;
				_end = _begin + oldsize;
				_endOfStorage = _begin + n;
			}
		}

		void resize(size_t n, const T& val = T())	//T()表示这种类型的默认值,比如int()=0
		{
			if (n <= size())
			{
				_end = _begin + n;
			}
			else
			{
				//如果n大于容量,调用reverse进行扩容
				if (n > capacity())
				{
					reserve(n);
				}
				//赋值val
				while (_end < _begin+ n)
				{
					*_end = val;
					_end++;
				}
			}
		}

		//在pos位置插入元素x
		void Insert(iterator pos, const T& x)
		{
			//1. 需要合法插入
			assert(pos <= _end);	//当pos和end相等的时候就是尾插push_back
			//2. 判断是否需要扩容
			if (_end == _endOfStorage)
			{
				//扩容导致迭代器失效,需要提前计算好pos的位置
				size_t newpos = pos - _begin;
				size_t newcapacity = capacity() == 0 ? 3 : capacity() * 2;
				reserve(newcapacity);
				pos = _begin + newpos; //重新设置pos
			}

			//插入元素
			iterator end = _end - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_end++;
		}

		//尾插
		void push_back(const T& x)
		{
			Insert(_end, x);
		}

		//获取元素数量
		size_t size()
		{
			return _end - _begin;
		}

		//获取容量
		size_t capacity()
		{
			return _endOfStorage - _begin;
		}

		//[]重载
		T& operator[](size_t i)
		{
			//注意要防止数组越界
			assert(i < size());
			return _begin[i];
		}

		//[]重载
		const T& operator[](size_t i)const
		{
			assert(i < size());
			return _begin[i];
		}
	private:
		iterator _begin;	    //数组开始
		iterator _end;	        //数组结尾
		iterator _endOfStorage; //容量结尾
	};
}

源文件 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "myVector.h"

void test1()
{
	YZC::myVector<int> arr;
	//插入20个随机值
	for (int i = 0; i < 20; i++)
	{
		arr.push_back(rand());
	}

	//遍历输出
	for (int i = 0; i < arr.size(); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	//使用[]修改
	for (int i = 0; i < 20; i++)
	{
		arr[i] = i;
	}

	for (int i = 0; i < arr.size(); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	//设置随机种子
	srand((unsigned int)time(0));
	test1();
	return 0;
}

运行结果如下:

 
2.8 erase和pop_back

        erase将pos后面的元素一个一个向前移动即可,最后让_end减去1

        而pop_back只要让_end减去1即可

		iterator& Erase(iterator pos)
		{
			assert(pos < _end);
			iterator it = pos;
			while (it != _end)
			{
				*it = *(it + 1);
				it++;
			}
			_end--;
			return pos;
		}

		void pop_back()
		{
			assert(_begin < _end);
			--_end;
		}

2.9 测试代码2

#define _CRT_SECURE_NO_WARNINGS 1
#include "myVector.h"

void test1()
{
	YZC::myVector<int> arr;
	for (int i = 0; i < 20; i++)
	{
		arr.push_back(i);
	}

	arr.pop_back();
	arr.pop_back();
	arr.pop_back();
	arr.push_back(1234);

	for (int i = 0; i < arr.size(); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	test1();
	return 0;
}

 

2.10 迭代器begin和end

		//开始位置的迭代器
		iterator begin()const
		{
			return _begin;
		}

		//结束位置后一个的迭代器
		iterator end()const
		{
			return _end;
		}

 有了begin和end后,我们就可以使用迭代器遍历和C++11 for循环

2.11 测试代码3

使用迭代器注意要将iterator设置为public

测试代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include "myVector.h"
#include <algorithm>
void test1()
{
	YZC::myVector<int> arr;
	//插入20个随机值
	for (int i = 0; i < 20; i++)
	{
		arr.push_back(rand() % 1000);
	}
	//1. 迭代器遍历
	YZC::myVector<int>::iterator it = arr.begin();
	while (it != arr.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//使用sort和迭代器进行排序
	sort(arr.begin(), arr.end());	
	//2. C++11for循环
	for (const auto& e : arr)
		cout << e << " ";
	cout << endl;
}

int main()
{
	//设置随机种子
	srand((unsigned int)time(0));
	test1();
	return 0;
}

 运行结果如下:

三. 测试类类型的变量

        上面的代码,经过测试,基本数据类型是没什么问题的。那么对于自定义类型或者系统自带的类型有没有错误呢?

        我们以string类为测试

#define _CRT_SECURE_NO_WARNINGS 1
#include "myVector.h"
#include <string>

//测试string类型
void test2()
{
	YZC::myVector<string> arr;
	//插入元素
	string s1 = "YZC";
	string s2 = "123";
	string s3 = "vector";
	string s4 = "string";
	string s5 = "abc";
	string s6 = "231141234";

	arr.push_back(s1);
	arr.push_back(s2);
	arr.push_back(s3);
	arr.push_back(s4);
	arr.push_back(s5);
	arr.push_back(s6);

	for (const auto& e : arr)
		cout << e << " ";
	cout << endl;

	//删除和修改元素
	arr[0] = "yzc";
	arr.pop_back();
	for (const auto& e : arr)
		cout << e << " ";
	cout << endl;
}

int main()
{
	test2();
	return 0;
}

测试结果如下: 

        注意:如果是自定义类型,必须要定义operator=之后才能插入数据,否则会报错!

四. vector完成动态二维数组 

        vector可以完成一个动态一维数组,那么如果使用vector定义一个二维数组?其实很简单,只要在将一个vector作为vector的元素不就可以了?

举例如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include "myVector.h"
#include <vector>

void test1()
{
	//vector中的元素是vector<int> 可以表示一个int的二维数组
	vector<vector<int>> arr;

	int index = 1;
	for (int i = 0; i < 6; i++)
	{
		vector<int> tmp;
		for (int j = 0; j < 6; j++)
		{
			tmp.push_back(index++);
		}
		arr.push_back(tmp);
	}

	for (int i = 0; i < 6; i++)
	{
		for (int j = 0; j < 6; j++)
		{
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}

}

int main()
{
	test1();
	return 0;
}

运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PDF文件怎么加密?如何给pdf文档加密码保护?(2025全新科普)

PDF文件因其跨平台兼容性和格式稳定性&#xff0c;成为广泛使用的文档格式。 然而&#xff0c;随着信息泄露风险的增加&#xff0c;如何保护PDF文件的安全成为了一个重要问题。 本文将介绍几种2025年最新的PDF文件加密方法&#xff0c;帮助用户为PDF文档添加密码保护。 一、使…

服务器端使用国密证书

服务器端国密证书nignx代理配置 1.资料准备 从gmssl实验室下载已经编译完成的gmssl包 地址&#xff1a;https://www.gmssl.cn/gmssl/index.jsp 下载位置&#xff1a; 保证openssl支持国密算法&#xff0c;ssl 1.1.1 已支持&#xff0c;检查一下最佳 准备一个支持国密的ngin…

第04章_运算符(基础)

1. 算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&#xff08;%&am…

如何寻找适合的HTTP代理IP资源?

一、怎么找代理IP资源&#xff1f; 在选择代理IP资源的时候&#xff0c;很多小伙伴往往将可用率作为首要的参考指标。事实上&#xff0c;市面上的住宅IP或拨号VPS代理IP资源&#xff0c;其可用率普遍在95%以上&#xff0c;因此IP可用率并不是唯一的评判标准 其实更应该关注的…

FinalShell工具数据备份升级、密码解密方法

前言 FinalShell 作为国产的服务器管理工具和远程终端软件。一个一体化的运维工具&#xff0c;在国内运维人员中还是比较受欢迎。它整合了多个常用功能&#xff0c;界面友好&#xff0c;使用方便。不过它是一个闭源的商业软件&#xff0c;虽然提供免费版本&#xff0c;但部分高…

华三(HCL)和华为(eNSP)模拟器共存安装手册

接上章叙述&#xff0c;解决同一台PC上同时部署华三(HCL)和华为(eNSP&#xff09;模拟器。原因就是华三HCL 的老版本如v2及以下使用VirtualBox v5版本&#xff0c;可以直接和eNSP兼容Oracle VirtualBox&#xff0c;而其他版本均使用Oracle VirtualBox v6以上的版本&#xff0c;…

0.shell 脚本执行方式

1.脚本格式要求 &#x1f951;脚本以 #!/bin/bash 开头 &#x1f966; 脚本要有可执行权限 2.执行脚本的两种方式 &#x1f96c; 方式1&#xff1a;赋予x执行权限 &#x1f952; ​​​​​​​方式2&#xff1a; sh执行 ​​​​​​​

EXCEL截取某一列从第一个字符开始到特定字符结束的字符串到新的一列

使用EXCEL中的公式进行特定截取 假设列A是一组产品的编码&#xff0c;我们需要的数据是“-”之前的字段。 我们需要在B1单元格输入公式“LEFT(A1,SEARCH("-",A1)-1)”然后选中B1至B4单元格&#xff0c;按“CTRLD”向下填充&#xff0c;就可以得出其它几行“-”之前的…

ComfyUI绘画|图生图工作流搭建

今天先分享到这里~ ComfyUI绘画|关于 ComfyUI 的学习建议

Css—实现3D导航栏

一、背景 最近在其他的网页中看到了一个很有趣的3d效果&#xff0c;这个效果就是使用css3中的3D转换实现的&#xff0c;所以今天的内容就是3D的导航栏效果。那么话不多说&#xff0c;直接开始主要内容的讲解。 二、效果展示 三、思路解析 1、首先我们需要将这个导航使用一个大…

书生大模型实战营第四期-入门岛-4. maas课程任务

书生大模型实战营第四期-入门岛-4. maas课程任务 任务一、模型下载 任务内容 使用Hugging Face平台、魔搭社区平台&#xff08;可选&#xff09;和魔乐社区平台&#xff08;可选&#xff09;下载文档中提到的模型&#xff08;至少需要下载config.json文件、model.safetensor…

服务器密码错误被锁定怎么解决?

当服务器密码错误多次导致账号被锁定时&#xff0c;解决方法需要根据服务器的操作系统&#xff08;如 Linux 或 Windows &#xff09;和具体服务器环境来处理。以下是常见的解决办法&#xff1a; 一、Linux 服务器被锁定的解决方法 1. 使用其他用户账号登录 如果有其他未被…

初识ProtoBuf以及环境搭建(Win和Ubuntu)

初始ProtoBuf 序列化和反序列化的概念 序列化&#xff1a;把对象转换为字节序列的过程 称为对象的序列化。 反序列化&#xff1a;把字节序列恢复为对象的过程 称为对象的反序列化。 什么情况下需要序列化和反序列化&#xff1f; 存储数据&#xff1a;当你想把的内存中的对象状…

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型)

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 目录 Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 1. 项目说明 2. 数据说明 &#xff08;1&#xff09;心跳信号分类预测数据集 3. 模型训练 &#xff08;1&#xff09;项目安装 &…

【Nginx】核心概念与安装配置解释

文章目录 1. 概述2. 核心概念2.1.Http服务器2.2.反向代理2.3. 负载均衡 3. 安装与配置3.1.安装3.2.配置文件解释3.2.1.全局配置块3.2.2.HTTP 配置块3.2.3.Server 块3.2.4.Location 块3.2.5.upstream3.2.6. mine.type文件 3.3.多虚拟主机配置 4. 总结 1. 概述 Nginx是我们常用的…

循环神经网络:从基础到应用的深度解析

&#x1f35b;循环神经网络&#xff08;RNN&#xff09;概述 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一种能够处理时序数据或序列数据的深度学习模型。不同于传统的前馈神经网络&#xff0c;RNN具有内存单元&#xff0c;能够捕捉序列中前后信息…

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)

使用vcpkg自动链接tinyxml2时莫名链接其他库&#xff08;例如boost&#xff09; vcpkg的自动链接功能非常方便&#xff0c;但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例&#xff0c;程序中调用tinyxml2的函数后&#xff0c;若vcpkg中同时存在opencv和…

鸿蒙开发-HMS Kit能力集(应用内支付、推送服务)

1 应用内支付 开发步骤 步骤一&#xff1a;判断当前登录的华为账号所在服务地是否支持应用内支付 在使用应用内支付之前&#xff0c;您的应用需要向IAP Kit发送queryEnvironmentStatus请求&#xff0c;以此判断用户当前登录的华为帐号所在的服务地是否在IAP Kit支持结算的国…

IDEA敲Web前端快捷键

1.html基础格式 英文符号TAB键 <!doctype html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport"content"widthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, mini…

字符串算法题

目录 题目一——14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; 1.1.两两比较 1.2.统一比较 题目二——5. 最长回文子串 - 力扣&#xff08;LeetCode&#xff09; 2.1.中心拓展算法 题目三——67. 二进制求和 - 力扣&#xff08;LeetCode&#xff09; 题目…