C++ —— vector 的模拟实现

news2024/9/22 23:14:11

目录

前言

1. vector深度剖析

 2. 基础框架

3. 核心接口

3.1 reserve

3.2 push_back 和 pop_back

3.3 print

 3.4 insert

3.5 erase

3.6 resize

4. 拷贝构造

4.1 构造与析构

4.2 拷贝构造 

4.3 赋值重载

4.4 迭代器区间

5. 使用memcpy拷贝问题


前言

接:C++ —— 关于vector-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/hedhjd/article/details/142334349?spm=1001.2014.3001.5501


1. vector深度剖析

 

 _start :相当于begin,指向开始的位置
_finish :相当于size,指向最后一个数据的位置
_end_of_storage :相当于_capacity


 2. 基础框架

//因为vector是用模板写的,所以不能进行声明和定义分离,否则会出现链接错误
#pragma once
#include<assert.h>

 // _start :相当于begin,指向开始的位置
//_finish :相当于size,指向最后一个数据的下一个位置
//_end_of_storage :相当于_capacity

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//迭代器
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		//const迭代器
		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}


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

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


		//可读可写
		T& operator[](size_t i)
		{
			assert(i < size());

			return _start[i];
		}

		//只读
		const T& operator[](size_t i) const
		{
			assert(i < size());

			return _start[i];
		}

		//判断是否为空
		bool empty()
		{
			return _start == _finish;
		}


	private:
		iterator _start = nullptr;//给个缺省值
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};


3. 核心接口

3.1 reserve

//修改之后
//扩容
void reserve(size_t n)
{
	if (n > capacity())
	{
		//提前将旧size赋给新size
		size_t old_size = size();
 
		//开辟新空间,创建临时变量
		//new出来的空间是初始化好的,所以可以直接用
		T* tmp = new T[n];

		//memcpy(tmp, _start, old_size * sizeof(T));
		/*如果T是string或者vector类型,就需要进行深拷贝
		 需要赋值进行拷贝数据,否则使用浅拷贝会导致内存泄漏
		*/
		for (size_t i = 0; i < old_size; i++)
		{
			//赋值就是string/vector的赋值,也就是深拷贝
			tmp[i] = _start[i];
		}
		//释放旧空间
		delete[] _start;
		//再把三个私有变量指向新空间
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}


3.2 push_back 和 pop_back

/*T有可能是一个int,也有可能是一个string,还可能是一个vector,
所以需要传&,传&不改变需要加上一个const*/
//尾插
void push_back(const T& x)
{
	//先判断需不需要扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	//如果不需要就直接插入到_finish的位置
	*_finish = x;
	++_finish;
}

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


3.3 print

如果给出的代码编译器不知道该代码是类型还是变量,所以导致识别不出来,那么可以在前面加上 typename 或者改为 auto 自动推导

//给print_vector套一层模板,让它被谁都可以使用
template<class T>
void print_vector(const vector<T>& v)
{
	//如果typename在没有实例化的类模板里面取东西,
	// 那么编译器不能区分这里const_iterator是类型还是静态成员变量
	//typename vector<T>::const_iterator it = v.begin();

	//那么使用auto可以自动推导,由v.begin()推导
	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

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

将print彻底转换成模板,使所有类型都可以使用,不管是string,vector等等 

//将print彻底转换成模板,使所有类型都可以使用,不管是string,vector等等
template<class Container>
void print_container(const Container& v)
{
	/*auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;*/

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

 3.4 insert

insert在扩容后原来的pos位置就会失效,也就是迭代器失效,就相当于成为了野指针,解决方法是将扩容之前的位置记录下来在扩容后更新pos的位置

//在指定位置插入数据
void insert(iterator pos, const T& x)
{
    assert(pos >= _start);
	assert(pos <= _finish);

	// 扩容
	if (_finish == _end_of_storage)
	{
		/*为了防止迭代器失效的情况,先创建一个临时变量len
		记录扩容之前pos - _start的位置*/
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		//然后再更新pos的位置
		pos = _start + len;
	}

	//将最后一个数据的位置给临时变量end
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;

	++_finish;

	return pos;
}


3.5 erase

erase之后也会出现迭代器失效,所以需要先记录pos原来的位置然后再更新pos

//删除指定位置的数据
void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it != end())
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;
}


3.6 resize

用于改变vector的有效长度,也可以用来初始化指定长度的指定数据

三种情况:

1. 当n < size()时,删除多余的元素,直接缩容

2. 当size() < n < capacity()时,直接插入数据

3. 当capacity() < n时,先扩容再插入数据

//用于改变vector的有效长度
//也可以开辟n个空间,使用val去初始化
void resize(size_t n, T val = T())
{
	//n < size,删除数据
	if (n < size())
	{
		_finish = _start + n;
	}
	else//>=size
	{
		//扩容
		reserve(n);
		while (_finish < _start + n)
		{
			//插入数据val
			*_finish = val;
			++_finish;
		}
	}
}


4. 拷贝构造

4.1 构造与析构


	//默认构造
    vector()
	{}

    // C++11 强制生成默认构造
	//vector() = default;



    
	//析构
	~vector()
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
	}


4.2 拷贝构造 


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


4.3 赋值重载

//赋值重载的传统写法
//clear清除数据但是不释放空间
void clear()
{
	_finish = _start;
}

// v1 = v3
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<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

// v1 = v3
vector<T>& operator=(vector<T> v)
{
	swap(v);

	return *this;
}


4.4 迭代器区间

1. 类模版里的成员函数还能继续是函数模版

2. 在迭代器区间构造的函数时可以使用函数模版,这样可以使用任意容器的迭代器初始化,例如链表

//迭代器区间
// 类模板的成员函数,还可以继续是函数模版
//加上一个模板那么迭代器就可以是任意类型的迭代器
//需要推导
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

//可以直接使用size_t类型  使用n个val初始化
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

//可以直接使用int类型  使用n个val初始化
vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}


5. 使用memcpy拷贝问题

memcpy拷贝问题的问题其实就是浅拷贝的原因

浅拷贝就是两个指针指向同一块空间,如果一个指针对该空间进行释放或者其他操作就会影响另一个指针导致内存泄漏等问题

所以最好使用深拷贝让两指针指向不同的空间然后使其数据保持一致

错误代码 


		//扩容
		//错误代码
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//提前将旧size赋给新size
				size_t old_size = size();

				//开辟新空间,创建临时变量
				//new出来的空间是初始化好的,所以可以直接用
				T* tmp = new T[n];

				//将旧空间里的数据拷贝给新空间
				//将 _start指向的size() * sizeof(T)空间里的数据拷贝到临时变量tmp里去
				memcpy(tmp, _start, size() * sizeof(T));

				//释放旧空间
				delete[] _start;
				//再把三个私有变量指向新空间
				_start = tmp;
				_finish = tmp + old_size;
				_end_of_storage = tmp + n;
			}
		}

修改之后

//修改之后
//扩容
void reserve(size_t n)
{
	if (n > capacity())
	{
		//提前将旧size赋给新size
		size_t old_size = size();

		//开辟新空间,创建临时变量
		//new出来的空间是初始化好的,所以可以直接用
		T* tmp = new T[n];

		//memcpy(tmp, _start, old_size * sizeof(T));
		/*如果T是string或者vector类型,就需要进行深拷贝
		 否则使用浅拷贝会导致内存泄漏
		*/
		for (size_t i = 0; i < old_size; i++)
		{
			//赋值就是string/vector的赋值,也就是深拷贝
			tmp[i] = _start[i];
		}
		//释放旧空间
		delete[] _start;
		//再把三个私有变量指向新空间
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

感谢观看~

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

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

相关文章

FX5 CPU模块和以太网模块的以太网通信功能

FX5 CPU模块和以太网模块的以太网通信功能的概要如下所示。 CPU模块的内置以太网端口的通信规格如下所示。 1、与MELSOFT的直接连接 不使用集线器&#xff0c;用1根以太网电缆直接连接以太网搭载模块与工程工具(GX Torks3)。无需设定IP地址&#xff0c;仅连接目标指定即可进行…

学习Java(一)类和对象

package demo.ceshi;public class Puppy {private int age;private String name;//构造器public Puppy( String name){this.name name;System.out.println("公主的名字叫&#xff1a;"name);}//设置age的值public void setAge(int age){this.age age;System.out.pr…

数值计算 --- 平方根倒数快速算法(中)

平方根倒数快速算法 --- 向Greg Walsh致敬&#xff01; 1&#xff0c;平方根倒数快速算法是如何选择初值的?WTF中的神秘数字究竟是怎么来的&#xff1f; 花开两朵&#xff0c;各表一枝。在前面的介绍中&#xff0c;我们已经知道了这段代码的作者在函数的最后使用了NR-iteratio…

CVE-2024-46103

前言 CVE-2024-46103 SEMCMS的sql漏洞。 漏洞简介 SEMCMS v4.8中&#xff0c;SEMCMS_Images.php的search参数&#xff0c;以及SEMCMS_Products.php的search参数&#xff0c;存在sql注入漏洞。 &#xff08;这个之前就有两个sql的cve&#xff0c;这次属于是捡漏了&#x1f6…

【MATLAB源码-第268期】基于simulink的永磁同步电机PMSM双闭环矢量控制系统SVPWM仿真,输出转速响应曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 永磁同步电机&#xff08;PMSM&#xff09;是目前工业领域中广泛使用的一种高效电机&#xff0c;其具有高功率密度、运行效率高、动态响应快等优点。在控制永磁同步电机时&#xff0c;通常采用矢量控制&#xff08;也称为磁场…

新160个crackme - 060-snake

运行分析 需破解Name和Serial PE分析 32位&#xff0c;未知程序和壳 点击Scan/t按钮外部扫描&#xff0c;发现是C程序 静态分析&动态调试 ida搜索关键字符串&#xff0c;双击进入 发现无法反编译 选中该函数&#xff08;地址&#xff1a;401048 - 401172&#xff09;Edit -…

认识结构体

目录 一.结构体类型的声明 1.结构的声明 2.定义结构体变量 3.结构体变量初始化 4.结构体的特殊声明 二.结构体对齐(重点难点) 1.结构体对齐规则 2.结构体对齐练习 (一)简单结构体对齐 (二)嵌套结构体对齐 3.为什么存在内存对齐 4.修改默认对齐数 三.结构体传参 1…

PMP--二模--解题--51-60

文章目录 14.敏捷--术语表--完成的定义DoD--它是团队需要满足的所有标准的核对单&#xff0c;只有可交付成果满足该核对单才能视为准备就绪可供客户使用。51、 [单选] 在冲刺计划会议上&#xff0c;Scrum主管重申&#xff0c;如果在冲刺结束时敏捷项目团队正在构建的产品增量没…

五种IO模型和阻塞IO

文章目录 五种 IO 模型和阻塞 IO1、五种 IO 模型1.1、阻塞 IO1.2、非阻塞 IO1.3、信号驱动 IO1.4、IO 多路转接1.5、异步 IO1.6、总结 2、高级 IO 概念2.1、同步通信&#xff08;synchronous communication&#xff09;和异步通信&#xff08;asynchronous communication&#…

第十五章:使用html、css、js编程制作一个网页版的下雪场景动画

背景:这是一个充满诗意的下雪场景代码。打开网页时,雪花轻轻飘落,覆盖住你的屏幕,仿佛置身于冬日的夜空下。背景音乐《我期待的不是雪》缓缓响起,伴随着雪花的飘动,仿佛心中的那份爱与温柔悄然绽放。 雪花的飘落是梦境般的存在,每一片雪花都是轻盈的告白,旋转着从天际…

使用GitHub Actions自动发布electron多端安装程序

GitHub Actions 是一个强大的自动化工具&#xff0c;可以帮助开发者在 GitHub 仓库中自动化构建、测试和部署工作流程。我们的客户端就是使用github action来打包项目发布的。 以下是关于 GitHub Actions 自动化构建的一些关键点和步骤&#xff1a; GitHub Actions 的基本概念…

go注册中心Eureka,注册到线上和线下,都可以访问

go注册中心Eureka&#xff0c;注册到线上和线下&#xff0c;都可以访问 本地通过127访问&#xff0c; 线上通过内网ip访问 package mainimport ("github.com/SimonWang00/goeureka""github.com/gin-gonic/gin""wbGo/controller""wbGo/task…

【工具变量】地市环保法庭试点城市DID数据集(2005-2023年)

数据简介&#xff1a;环保法庭是中国司法体系中专门处理环境资源案件的审判机构&#xff0c;其主要职责包括审理涉及自然环境污染、矿产资源保护、自然资源环境开发等环境资源民事纠纷案件&#xff0c;对不服下级人民法院生效裁判的环境资源民事案件进行审查&#xff0c;以及对…

Java_Se--方法

方法就是一个代码片段. 类似于 C 语言中的 "函数"。方法存在的意义(不要背, 重在体会): 1. 是能够模块化的组织代码 ( 当代码规模比较复杂的时候 ). 2. 做到代码被重复使用 , 一份代码可以在多个位置使用 . 3. 让代码更好理解更简单 . 4. 直接调用现有方法开…

cv中每个patch的关联

在计算机视觉任务中&#xff0c;当图像被划分为多个小块&#xff08;patches&#xff09;时&#xff0c;每个 patch 的关联性可以通过不同的方法来计算。具体取决于使用的模型和任务&#xff0c;以下是一些常见的计算 patch 关联性的方法&#xff1a; 1. Vision Transformer (…

IDA Pro-代码结构识别

Lab06-01.exe分析 1.由main 函数调用的唯一子过程中发现的主要代码结构是什么? if语句结构 找到main函数中唯一调用的函数&#xff0c;并进入 判断网络是否链接成功&#xff0c;如果返回0走右边未连接成功 2.位于0x40105F的子过程是什么? 将字符串压栈&#xff0c;猜测…

双非本 985 硕士,秋招上岸字节算法岗!

最近已有不少大厂都在秋招宣讲了&#xff0c;也有一些在 Offer 发放阶段。 节前&#xff0c;我们邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对新人如何快速入门算法岗、如何准备面试攻略、面试常考点、大模型项目落地经验分享等热门话题进行了深入的讨论。…

面向对象程序设计——set容器の简析

1.set的介绍 • 序列式容器和关联式容器 • 我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、array、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间⼀般没有紧…

Python GUI 编程:tkinter 初学者入门指南——窗口

目录&#xff1a; 创建窗口更改窗口标题更改窗口大小和位置窗口在屏幕上居中窗口设置的其他属性 Tkinter 是在 Python 中开发 GUI&#xff08;图形用户界面&#xff09;最常用的库。在本指南中&#xff0c;我们将引导您了解 Tkinter 的基本知识&#xff0c;学习如何使用 Tkinte…

汽车电子零部件(16):ZCU区域控制器

ZCU(Zone Control Unit,区域控制器),功能主要包括哦数据交互、信号控制及电力分配等,是智能网联汽车中不可或缺的关键组件,ECU负责车身、车门、车窗、天窗、车灯(外大灯、内氛围灯)、座椅(可能包括座椅音响)、雷达甚至后排娱乐系统等控制执行单元的集中化。 CCU(centr…