C++ vector类模拟实现

news2024/11/17 22:39:23

目录

一、成员变量

二、构造函数

1.默认构造 

 2.拷贝构造

3.迭代器构造

4.使用n个值构造

5.赋值拷贝

三、析构函数 

四、vector重要成员函数

1.size和capacity函数

 2.reserve函数

 3.resize函数

 4.push_back函数

5.insert函数

6.erase函数 

7.重载operator[]


一、成员变量

STL库里面,vector的成员变量和string有一些不一样,他的成员变量是用了迭代器

_start是数组起始位置

_finish是数据内容结束位置的下一个

_endofstorage是开辟的空间结束位置的下一个。

 

那么vector的迭代器是又是什么呢?

vector使用了模版,他可以适配各种类型,他的迭代器就是这个模板类型的指针

 因为vector和string一样,本质上就是数组,开辟的空间在内存上是连续的,因此使用指针当迭代器是在合理不过了。

二、构造函数

我们模仿一下STL里面的vector,没有使用适配器。

1.默认构造 

vector的默认构造很简单,直接写上就好,内容都可以不需要,因为我们在成员变量哪里给到了初始值,他会在初始化列表中自动调用,因此这里可以不需要写初始化列表。

那么我们可以不可以不要下面这局代码呢?   

答案是不可以的,因为我们后续还会写上其他的构造函数,那么编译器就不会提供默认生成的构造函数了。那这样我们普通的一个代码  vector<int> v  就会报错

vector()
{}

 2.拷贝构造

拷贝构造调用了push_back函数,先开辟好空间,防止后续再扩容,后续尾插即可。

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

3.迭代器构造

这里使用了模板来处理,因为我们不仅仅想使用vector迭代器去构造类对象,我们还想使用其他类对象的迭代器去构造。也是利用尾插即可。

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

4.使用n个值构造

开辟n个空间,并全部赋值为给到的val参数。

vector(int n, const T& val = T())
{
	resize(n, val);
}

vector(size_t n, const T& val = T())
{
	resize(n,val);
}

 这里为什么我们要重载呢,一个写出int类型,一个写成size_t类型?

我们先不写上面的int重载,下面这句代码,使用n个val进行拷贝,竟然报错了,为啥会这样?

48行有问题,我们发现,明明我想调用的是58行的拷贝构造,为啥会到模板那里去?

是因为此时所传的 10 和 1 都是int类型,而下面那个是 size_t和 int 两个类型,编译器认为你要用这个模板来初始化,因此会调用生成 InputIterator为int的函数,int类型去解引用,那可不就报错了嘛,为了让编译器认识两个整形,我们就重载了vector,使他能够知道我们的意思。

5.赋值拷贝

利用了很现代的写法,参数我们不传&,就会拷贝构造一个临时对象,这个临时对象刚好是我this需要的内容,我直接和你swap一下,你身上就有了我不要的内容了,同时出了作用域你会析构,我会引用返回,就完成了赋值拷贝。

vector<T>& operator=(vector<T> v)
{
	swap(_start,v._start);
	swap(_finish, v._finish);
	swap(_endofstorage, v._endofstorage);
	return *this;
}

三、析构函数 

easy,直接delete[]  ,顺便置空即可。

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

四、vector重要成员函数

1.size和capacity函数

由于我们成员变量都是迭代器(实现方式为指针),因此size()的大小和capacity()都可以用迭代器相减的方式得到。

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

 2.reserve函数

reserve可以开辟空间,n比现在的空间大才开辟,比现在的空间小则不管,不会删除数据。

如果需要开辟空间,则会先new出新的空间,把原有的数据数据拷贝到这个空间里,再删除原有的数据,同时对成员变量进行修改。

注意我们在拷贝的时候不能使用memcpy,因为用memcpy拷贝,如果类型为自定义类型,就仅仅是浅拷贝,浅拷贝会在开辟新空间的时候进行delete,一旦delete,新空间也被delete了。

这里我们选择了使用循环赋值,因为自定义类型会调用他的赋值拷贝,而他的赋值拷贝一般是深拷贝(只要写了的话),因此我们再扩容的时候就不会因为delete而发生错误了。

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy对自定义类型是浅拷贝
			//memcpy(tmp, _start, sz * sizeof(T));
			//循环赋值,会调用自定义类型的赋值拷贝,就是深拷贝
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_endofstorage = _start + n;
	}
}

 3.resize函数

resize会改变vector的size()。

如果传的n要比size()小,那么就变成这个长度。

如果n比size()大,就要先看扩不扩容,但是无论扩不扩容,我们都可以直接调用reserve函数,需要扩你就扩,不需要也没啥影响,后面在将你传过来的值,赋值给还未初始化的空间。

注意在C++中一切类型都算对象,包括内置类型,因此我们传参给到的默认参数为T()  就像一个类的默认构造一样。 

void resize(size_t n,const T& val = T())
{
	if (n <= size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		for (size_t i = size(); i < n; i++)
		{
			_start[i] = val;
		}
		_finish = _start + n;
	}
}

 4.push_back函数

push_back函数先判断空间满了没有,满了就去扩容,空间capacity()为0就给4,不为0就给2倍。

然后再*_finish这里赋值,_finish++即可。

void push_back(const T& x)
{
	if (size() == capacity())
	{
		size_t cp = capacity() == 0 ? 4 : capacity() * 2;
		reserve(cp);
	}
	*_finish = x;
	_finish++;
}

5.insert函数

insert函数传的参数不是索引,而是迭代器!!!

只要有插入,我们都得判断是否扩容,由于这里传递的是迭代器(vector中是指针),扩容有可能是异地扩容,因此地址会发生改变,我们扩容前先记录一下pos相对于_start的位置,扩容后再加上这个位置赋值给pos才对。

后续就是挪动数据,挪动完赋值,_finish++,最后要返回pos(可以防止迭代器失效)。

iterator insert(iterator pos,const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (size() == capacity())
	{
		size_t len = pos - _start; 
		//扩容后_start位置可能会发生改变了,因此要记录pos的相对位置,改变pos的值
		size_t cp = capacity() == 0 ? 4 : capacity() * 2;
		reserve(cp);
		pos = _start+len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1)  = *end;
		end--;
	}
	*pos = x;
	_finish++;
	return pos;
}

6.erase函数 

erase比insert还要简单一点,也是挪动数据,方向不一样,insert是从后往前数据往后挪动,erase是从当前位置往后向前挪动数据。再_finish--,最后返回pos(这也是为了防止迭代器失效)。

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

7.重载operator[]

 分为普通版本和const版本,普通可读可写,const只能读。

T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}
//不可修改
const T& operator[](size_t pos) const
{
	assert(pos < size());
	return _start[pos];  
}

最后附上总代码 

vector.h

#pragma once
namespace kky
{
	template<class T>
	class vector
	{
	public:
		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()
		{}

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

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

		vector(int n, const T& val = T())
		{
			resize(n, val);
		}

		//vector(size_t n, const T& val = T())
		//{
		//	resize(n,val);
		//}

		vector<T>& operator=(vector<T> v)
		{
			swap(_start,v._start);
			swap(_finish, v._finish);
			swap(_endofstorage, v._endofstorage);
			return *this;
		}


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

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy对自定义类型是浅拷贝
					//memcpy(tmp, _start, sz * sizeof(T));
					//循环赋值,会调用自定义类型的赋值拷贝,就是深拷贝
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

		void resize(size_t n,const T& val = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				for (size_t i = size(); i < n; i++)
				{
					_start[i] = val;
				}
				_finish = _start + n;
			}
		}

		void push_back(const T& x)
		{
			if (size() == capacity())
			{
				size_t cp = capacity() == 0 ? 4 : capacity() * 2;
				reserve(cp);
			}
			*_finish = x;
			_finish++;
		}

		iterator insert(iterator pos,const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (size() == capacity())
			{
				size_t len = pos - _start; 
				//扩容后_start位置可能会发生改变了,因此要记录pos的相对位置,改变pos的值
				size_t cp = capacity() == 0 ? 4 : capacity() * 2;
				reserve(cp);
				pos = _start+len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1)  = *end;
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}

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

		
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		//不可修改
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];  
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	};
	void test01()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;
		for (auto e : v)
		{
			cout << e << " ";
		}
	}
	void test02()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.resize(10, 6);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		v.insert(v.begin(), 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test03()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		v.erase(v.begin());
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		v.erase(v.begin()+2);
		for (auto e : v)
		{
			cout << e << " ";
		}
	}
	void test04()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.erase(it);
			}
			else
			{
				it++;
			}
		}
		for (auto e : v)
		{
			cout << e << " ";
		}
	}
	void test05()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);
		vector<int> v2(v);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;
		vector<int> v3;
		v3.push_back(1);
		v3 = v;
		for (auto e : v3)
		{
			cout << e << " ";
		}
	}
	void test06()
	{
		vector<int> v(10, 1);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		std::string s("123456");
		vector<int> v1(s.begin(), s.end());
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test07()
	{
		vector<int>v1(10, 1);
		vector<int>v2(10, 2);
		v1 = v2;
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}


test.cpp

#include <iostream>
using namespace std;
#include<cassert>
#include"vector.h"
int main()
{
	kky::test07();

}

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

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

相关文章

html 按钮点击倒计时,限制不可点击

html 按钮点击倒计时&#xff0c;限制不可点击 e94cbabd25cfc7f3f53a50a235734c22.jpg <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title></title></head&…

Ubuntu20.4 设置代理

主要是涉及2个代理 涉及apt 可以在、/etc/apt/apt.conf 中进行修改 在系统全局可以在/etc/profile中进行修改

2023年最新全国各省行政区划数据(省-市-区县-乡镇-村)

背景 现实情况&#xff0c;在信息系统开发、电子商务平台、app等等相关软件开发&#xff0c;都会设计到行政区数据联动&#xff0c;这里已经爬好全国各省行政区划数据可供下载。 数据来源 内容为2023年全国统计用区划代码&#xff08;12位&#xff09;和城乡分类代码&#xff…

跨境电商:产业带的深度赋能

近年来&#xff0c;中国跨境电商平台崭露头角&#xff0c;成为推动国内产业带转型升级和出海的新引擎。这一充满活力的领域不仅让中国制造走向世界&#xff0c;也为国内众多产业提供了数字化升级的机会&#xff0c;实现了“小单快反”和按需供应。 专业跨境电商平台如SHEIN和阿…

RabbitMQ整理

MQ(Message Queue)&#xff1a;是队列&#xff0c;也是跨进程的通信机制&#xff0c;用于上下游传递信息 FIFO(First In First Out)&#xff1a;先进先出 RabbitMQ访问&#xff1a;http://127.0.0.1:15672/ 默认账号密码&#xff1a;guest 优势&#xff1a;流量削峰&#x…

计网----使用代码实现C/S模型的数据通信,IP地址分类,子网掩码,网关,广播地址,非默认子网掩码,子网划分的常见问题

计网----使用代码实现C/S模型的数据通信,IP地址分类,子网掩码,网关,广播地址,非默认子网掩码,子网划分的常见问题 一.使用代码实现C/S模型的数据通信&#xff08;七层网络系统的博客https://blog.csdn.net/m0_73483024/article/details/133916201?spm1001.2014.3001.5502的后…

completablefuture的使用

CompletableFuture使用详解 【Java异常】Variable used in lambda expression should be final or effectively final CompletableFuture原理与实践-外卖商家端API的异步化 项目描述 项目接口需要从下游多个接口获取数据&#xff0c;并且下游的网络不稳定还会涉及到循环调用…

DC电源模块的模拟电源对比数字电源的优势有哪些?

BOSHIDA DC电源模块的模拟电源对比数字电源的优势有哪些&#xff1f; DC电源模块是一种电子元件&#xff0c;用于将交流电转换为直流电&#xff0c;以供电路板、集成电路等电子设备使用。在直流电源模块中&#xff0c;有模拟电源和数字电源两种类型。 模拟电源是一种传统的电源…

如何使双核心的ESP32开启双核功能同时执行多任务

如何使双核心的ESP32开启双核功能同时执行多任务 简介查看ESP32当前哪一个内核在执行任务双核同时执行任务总结 简介 ESP32-WROOM-32模组内置两个低功耗 Xtensa 32-bit LX6 MCU&#xff0c;两个 CPU 核&#xff08;core 0与core 1&#xff09;可以被单独控制。可以在两个内核上…

PHP数据加密传输和存储问题

PHP数据加密的类型 md5()&#xff0c;sha1()&#xff0c;crypt() 双md5加密加盐

2、Kafka 生产者

3.1 生产者消息发送流程 3.1.1 发送原理 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。在 main 线程 中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c; Sender 线程不断从 RecordAccumulator 中…

内衣洗衣机有危害吗?迷你洗衣机热销第一名

现在洗内衣内裤也是一件较麻烦的事情了&#xff0c;在清洗过程中还要用热水杀菌&#xff0c;还要确保洗衣液是否有冲洗干净&#xff0c;还要防止细菌的滋生等等&#xff0c;所以入手一款小型的烘洗全套的内衣洗衣机是非常有必要的&#xff0c;专门的内衣洗衣机可以最大程度减少…

vue3 + axios 中断取消接口请求

前言 最近开发过程中&#xff0c;总是遇到想把正在请求的axios接口取消&#xff0c;这种情况有很多应用场景&#xff0c;举几个例子&#xff1a; 弹窗中接口请求返回图片&#xff0c;用于前端展示&#xff0c;接口还没返回数据&#xff0c;此时关闭弹窗&#xff0c;需要中断接…

【神印王座】伊莱克斯现身,龙皓晨获得一传承,圣采儿却惨遭反噬

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 神印王座第78集的预告片更新&#xff0c;一个神秘人物伊莱克斯突然现身&#xff0c;他是一个实力强大、统兵能力卓越的统帅&#xff0c;更拥有令人闻风丧胆的亡灵大军。这个传奇人物被誉为永恒之塔的主人&…

Linux下的命令行参数和环境变量

命令行参数 什么是命令行参数 命令行参数是指在执行命令行程序时&#xff0c;给程序传递的额外参数。在Linux终端中&#xff0c;命令行参数通常通过在命令后面添加空格分隔的参数来传递。 Linux下以main函数举例说明 #include<stdio.h>int main(int argc char* argv[])…

svg常用属性及动画效果

文章目录 引言一、什么是svg二、svg有哪些应用场景三、基本使用四、svg常用属性介绍1. viewBox2. preserveAspectRatio3. symbol标签4.transform1. translate2.scale3.rotate4.skewX和skewY 5. matrix6.stroke-dasharray 五、svg动画1. svg属性结合css动画2. animate 标签动画3…

【网络】网络编程套接字(一)

网络编程套接字 一 一、网络编程中的一些基础知识1、认识端口号2、认识TCP协议和UDP协议3、网络字节序 二、socket编程1、sockaddr结构2、简单的UDP网络程序Ⅰ、服务器的创建Ⅱ、运行服务器Ⅲ、关于客户端的绑定问题Ⅳ、启动客户端Ⅴ、本地测试Ⅵ、网络测试 一、网络编程中的一…

20款VS Code实用插件推荐

前言&#xff1a; VS Code是一个轻量级但功能强大的源代码编辑器&#xff0c;轻量级指的是下载下来的VS Code其实就是一个简单的编辑器&#xff0c;强大指的是支持多种语言的环境插件拓展&#xff0c;也正是因为这种支持插件式安装环境开发让VS Code成为了开发语言工具中的霸主…

升级常有,错误不可避免!更新升级时遇到的错误0x800F081F–0x20003如何修复

Windows更新错误0x800F081F–0x20003主要发生在升级Windows 11/10副本时。这是由于在计算机上启用各种与开发人员相关的设置时出现错误造成的。安装程序遇到的错误被表述为以下任一错误: 我们无法安装Windows 11/10。SAFE_OS阶段的安装失败,INSTALL_UPDATES操作期间出现错误:…

解除OU屏蔽(EBS检查无法直接查询解决)

解除OU屏蔽(EBS检查无法直接查询解决) 具有OU屏蔽的例子 SELECT t.org_id, t.* FROM po.po_headers_all t -- 无屏蔽表&#xff0c;在PL/SQL运行有数据 SELECT t.org_id, t.* FROM apps.po_headers t -- 包含OU屏蔽&#xff0c;在PL/SQL中查询无数据多组织屏蔽原理 1. 在…