【C++ STL】模拟实现 vector

news2024/9/28 1:18:02

标题:【C++ STL】模拟实现 vector

@水墨不写bug

 (图片来源于网络)


正文开始:

        STL中的vector是一个动态数组,支持随机访问,可以根据需要来扩展数组空间。

        本项目将实现vector的部分常用功能,以增强对vector的熟悉程度,了解STL容器的工作原理,积累项目经验,也为将来自主实现和改造容器奠定坚实的基础。

        STL内实现的vector是一个类模板,也就是vector的数据类型理论上可以是任意的数据类型,本文力求与STl采取类似的方法,通过类模板来实现vector。更接近于STL的实现方法,自然会让你对STL有更深的理解。

一、明确方式,铺平道路

1.文件问题 

以前我们在实现string的时候,采用分文件操作:

string.h

string.cpp

        这是一个非常好的习惯,无疑为在项目进展,和后期维护时提供了便利。但是我们要实现的vector是通过类模板来实现的,如果还将vector类内部的函数的声明与定义分离,就会出现问题:

        由于类模板在编译时不会实例化,所以当我们想要调用这个类的成员函数的时候,就会发现没有匹配的成员函数,没有办法调用成员函数。于是,在用类模板实现vector时,我们不再分为vector.h和vector.cpp两个文件,而是将h和cpp文件合并为一个h文件,这样在同一个类模板中就可以调用成员函数了。

二、vector功能简介

I、构造函数和析构函数

  1. 默认构造函数:创建一个空的vector对象。
  2. 带大小和初始值的构造函数:创建一个包含指定数量元素的vector,每个元素都被初始化为相同的值。
  3. 范围构造函数:通过迭代器或指针的范围来初始化vector。
  4. 拷贝构造函数:使用另一个vector对象来初始化新的vector对象。
  5. 移动构造函数(C++11及以后):使用另一个vector对象的资源来初始化新的vector对象,同时使原对象变为空。
  6. 初始化列表构造函数(C++11及以后):使用初始化列表来初始化vector。
  7. 析构函数:销毁vector对象,释放其占用的内存。

II、迭代器

  1. begin():返回指向vector第一个元素的迭代器。
  2. end():返回指向vector最后一个元素之后位置的迭代器(不是最后一个元素)。
  3. rbegin():返回指向vector最后一个元素的反向迭代器。
  4. rend():返回指向vector第一个元素之前位置的反向迭代器。
  5. cbegin() 和 cend():与begin()和end()类似,但返回的迭代器是const类型,不能用于修改元素。

III、容量操作

  1. size():返回vector中元素的当前数量。
  2. max_size():返回vector能够容纳的最大元素数量(通常是一个很大的值,但具体取决于系统和编译器的实现)。
  3. capacity():返回vector当前分配的存储容量,可能大于或等于size()返回的值。
  4. reserve(n):请求vector的存储容量至少为n,如果当前容量小于n,则重新分配内存。
  5. shrink_to_fit()(C++11及以后):尝试将vector的capacity减少为其当前size的大小,但不一定成功,因为释放内存是可选的。

IV、修改容器

  1. push_back(value):在vector的末尾添加一个元素。
  2. pop_back():移除vector的最后一个元素。
  3. insert(pos, value):在指定位置pos之前插入一个元素value。
  4. erase(pos):移除指定位置pos的元素,并返回指向被移除元素之后位置的迭代器。
  5. clear():移除vector中的所有元素,使其变为空。
  6. assign(first, last):用范围[first, last)内的元素替换vector的内容。
  7. assign(n, value):用n个值为value的元素替换vector的内容。

V、元素访问

  1. operator[]:通过下标访问vector中的元素。
  2. at(pos):通过位置pos访问vector中的元素,并进行范围检查。
  3. front():返回vector中第一个元素的引用。
  4. back():返回vector中最后一个元素的引用。
  5. data():返回指向vector中第一个元素的指针(C++11及以后)。

VI、其他操作

  1. swap(other):交换两个vector的内容。
  2. find(value):在vector中查找值为value的第一个元素,并返回指向该元素的迭代器,如果未找到则返回end()。
  3. sort():对vector中的元素进行排序。
  4. reverse():颠倒vector中元素的顺序。

 三、实现

        通过本文,你可以跟随我的思路来了解实现 vector 的底层思路,以及实现的原理。

        由于我们将vector实现在一个 .h 文件,并且要实现类模板vector,于是我们先写出框架: 先定义模板参数;

template<typename T>
class vector
{

private:
	T* _start;//数组的开始位置
	T* _finish;//数组内最后一个数据的下一个位置,finish-start表示数组内元素个数
	T* _end_of_storage;
    //数组最后一个能存储元素的下一个位置,end_of_storage - start 表示数组的容量
};

         一个类,想要创建一个对象,必须要有构造函数

        STL的vector在实例化之后,默认是已经开辟好了空间,只不过size == 0 ,及内部没有数据而已。

        这里我们化繁为简,在构造函数内部不开辟空间,而是在使用或者说对vector对象进行操作的时候再开辟动态空间,于是我们可以直接在变量声明时给默认值并且使用不传参的默认构造函数:

template<typename T>
class vector
{
    vector() = default;
private:
	T* _start = nullptr;
	T* _finish = nullptr;
	T* _end_of_storage = nullptr;
};

 

        push_back是对vector进行最基本的操作,想要实现push_back,则需要考虑扩容逻辑:

如果vector的size() == capacity ()则表示vector已经满了,需要进行扩容,扩容是多次进行的,我们就单独将扩容用的reserve()实现出来即可;(上文加黑即为要实现的函数)

 

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

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

//保留空间
void reserve(int n)
{
	//先保存size,防止_start,_finish变化,导致size无法计算
	int oldsize = size();
	//要求保留的大于现有的,扩容
	if (n > capacity())
	{
		T* tem = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < oldsize; i++)
			{
				tem[i] = _start[i];
			}
		}

		if(_start)
			delete[] _start;
		_start = tem;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
	//否则,不缩容
}

在实现了这些函数之后,就可以实现push_back()函数的逻辑了。当然,有了尾插,就少不了尾删:pop_back(),由于尾删的逻辑简单,直接给出代码:


//尾插一个T对象
void push_back(const T& t)
{
	if (size() == capacity())
	    //需要扩容
	{
        //二倍扩容逻辑
		int Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(Newcapacity);
		//改变capacity,不改变size
	}
	//扩容完毕,开始尾插
	*_finish = t;
	++_finish;
}


void pop_back()
{
	assert(size() > 0);

	--_finish;
}

        STL内的vector是支持迭代器访问的,也就是支持范围for;由于范围for在编译的时候会自动找begin()end(),所以我们需要定义迭代器iterator,同时实现begin(),end(),为使用范围for做准备:

//迭代器
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使用的是编译器默认生成的成员函数,在大多数情况下会出现问题:

        比如:容器内部是指向堆区的指针,使用默认构造会导致浅拷贝的问题。

这就给于我们警示:默认成员函数能自己手动实现,就自己手动实现。

//构造函数,迭代器区间初始化
template<typename inputIterator>
//支持任意容器的迭代器初始化:string
vector(const inputIterator& begin,const inputIterator& end)
	//左闭右开
{
	reserve(end-begin);
	iterator it = begin;
	while (it != end)
	{
		push_back(*it);
		++it;
	}
}

//整形匹配
vector(int n, const T& val = T())
{
	
	reserve(n);
	//reserve不改变capacity
	for (int i = 0; i < n; i++)
	{
		_start[i] = val;
	}
	//用多少申请多少,size等于容量
	_finish = _end_of_storage = _start + n;
}
//自定义类型匹配
vector(size_t n,const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		_start[i] = val;
	}
}
//拷贝构造
vector(const vector<T>& t)
	//目标,将t深拷贝给*this
{
	reserve(t.capacity());
	for (const auto& e : t)
	{
		push_back(e);
	}
}
void swap(vector<T> tem)
{
	std::swap(_finish, tem._finish);
	std::swap(_start, tem._start);
	std::swap(_end_of_storage, tem._end_of_storage);
}

//赋值重载
vector<T> operator=(const vector<T> tem)
	//拷贝构造创建一个临时对象,用于交换得到有效数据
	// 只要完成拷贝构造即可完成赋值重载
{
	//现代写法
	swap(tem);
	return *this;
}
//析构函数
~vector()
{
	if(_start)
		delete _start;
	_finish = _end_of_storage = nullptr;
}

 接下来我们还需要实现vector内部对象的随机访问,由于vector内部的数据类型是模板,数据类型不能确定,这就需要我们重载 [] 操作符,也就是实现operator[]函数:

//一般类型调用,可读可写
T& operator[](size_t pos)
{
	//空间地址有效
	assert(pos < size() && pos >= 0);

	return _start[pos];
}

//const对象调用的,read-only
const T& operator[](size_t pos) const
{
	//空间地址有效
	assert(pos < size() && pos >= 0);

	return _start[pos];
}

 仅仅有了尾插和尾删是不够的,我们还要实现任意位置的插入删除:

//在pos位置插入对象
iterator insert(iterator pos, const T& t)
	//由于可能需要扩容,会发生迭代器失效,对内部而言
	//迭代器pos在扩容前后指向的对象不再相同,对外部也是同样的会发生
{
	if (size() == capacity())
		//需要扩容
	{
		int len = pos - _start;
		int Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(Newcapacity);
		//改变capacity,不改变size

		//记录len,解决迭代器失效的问题
		pos = _start + len;
	}

	//移动对象
	iterator end = _finish;
	while (end != pos)
	{
		*end = *(end - 1);
		--end;
	}
	*pos = t;

	return pos;
}

//一般不会出现迭代器失效的问题
iterator erase(iterator pos)
{
	iterator oldpos = pos;
	iterator start = pos + 1;
	while (start < _finish)
	{
		*(start - 1) = *start;
		++start;
	}
	--_finish;
	return oldpos;
}

        接下来,为了避免命名冲突,体现封装,我们要将上述实现的vector封装在我们自己的命名空间中,我的命名空间的名称为:ddsm

STL的vector模拟实现:

#pragma once
#include<iostream>
#include<cstring>
#include<cassert>
using namespace std;
namespace ddsm
{
	template<typename T>
	class vector
	{
	public:
		vector() = default;

		//构造函数,迭代器区间初始化
		template<typename inputIterator>
		//支持任意容器的迭代器初始化:string
		vector(const inputIterator& begin,const inputIterator& end)
			//左闭右开
		{
			reserve(end-begin);
			iterator it = begin;
			while (it != end)
			{
				push_back(*it);
				++it;
			}
		}

		//整形匹配
		vector(int n, const T& val = T())
		{
			
			reserve(n);
			//reserve不改变capacity
			for (int i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			//用多少申请多少,size等于容量
			_finish = _end_of_storage = _start + n;
		}
		//自定义类型匹配
		vector(size_t n,const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				_start[i] = val;
			}
		}
		//拷贝构造
		vector(const vector<T>& t)
			//目标,将t深拷贝给*this
		{
			reserve(t.capacity());
			for (const auto& e : t)
			{
				push_back(e);
			}
		}
			防止迭代器失效
			//int _len = t.size();
			//int _capacity = t.capacity();
			
			//将原t内容拷贝到tem
			//iterator tem = new T[_capacity];
			//iterator ptem = tem;
			//iterator start = t._start;

			//while (start != t._finish)
			//{
			//	*ptem = *start;
			//	++ptem;
			//	++start;
			//}

			tem给*this
			//_start = tem;
			//_finish = _start + _len;
			//_end_of_storage = _start + _capacity;

		/*
			iterator tem = new T[capacity()];
			iterator ptem = tem;
			iterator start = t._start;

			//将原vector内容拷贝到新vector
			while (start != t._finish)
			{
				*ptem = *start;
				++ptem;
				++start;
			}*/
		void swap(vector<T> tem)
		{
			std::swap(_finish, tem._finish);
			std::swap(_start, tem._start);
			std::swap(_end_of_storage, tem._end_of_storage);
		}

		//赋值重载
		vector<T> operator=(const vector<T> tem)
			//拷贝构造创建一个临时对象,用于交换得到有效数据
			// 只要完成拷贝构造即可完成赋值重载
		{
			//现代写法
			swap(tem);
			return *this;
		}
		//迭代器
		typedef T* iterator;
		typedef const T* const_iterator;

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

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

		iterator begin() 
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//保留空间
		void reserve(int n)
		{
			//先保存size,防止_start,_finish变化,导致size无法计算
			int oldsize = size();
			//要求保留的大于现有的,扩容
			if (n > capacity())
			{
				T* tem = new T[n];
				if (_start)
				{
					for (size_t i = 0; i < oldsize; i++)
					{
						tem[i] = _start[i];
					}
				}

				if(_start)
					delete[] _start;
				_start = tem;
				_finish = _start + oldsize;
				_end_of_storage = _start + n;
			}
			//否则,不缩容
		}

		//尾插一个T对象
		void push_back(const T& t)
		{
			if (size() == capacity())
				//需要扩容
			{
				int Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(Newcapacity);
				//改变capacity,不改变size
			}
			//扩容完毕,开始尾插
			*_finish = t;
			++_finish;
		}

		//一般类型调用,可读可写
		T& operator[](size_t pos)
		{
			//空间地址有效
			assert(pos < size() && pos >= 0);

			return _start[pos];
		}

		//const对象调用的,read-only
		const T& operator[](size_t pos) const
		{
			//空间地址有效
			assert(pos < size() && pos >= 0);

			return _start[pos];
		}

		void pop_back()
		{
			assert(size() > 0);

			--_finish;
		}

		//在pos位置插入对象
		iterator insert(iterator pos, const T& t)
			//由于可能需要扩容,会发生迭代器失效,对内部而言
			//迭代器pos在扩容前后指向的对象不再相同,对外部也是同样的会发生
		{
			if (size() == capacity())
				//需要扩容
			{
				int len = pos - _start;
				int Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(Newcapacity);
				//改变capacity,不改变size

				//记录len,解决迭代器失效的问题
				pos = _start + len;
			}

			//移动对象
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				--end;
			}
			*pos = t;

			return pos;
		}

		//一般不会出现迭代器失效的问题
		iterator erase(iterator pos)
		{
			iterator oldpos = pos;
			iterator start = pos + 1;
			while (start < _finish)
			{
				*(start - 1) = *start;
				++start;
			}
			--_finish;
			return oldpos;
		}

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

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	//非成员函数,流插入
	ostream& operator<<(ostream& out, vector<int> v)
	{
		for (const auto& e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		return out;
	}


};

完·~

未经作者同意禁止转载 

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

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

相关文章

veriloga要怎么在candence中编译和加密?

从编译器的角度来说&#xff0c;我在ADS中可能就是直接用notepad编写&#xff0c;然后生成检查&#xff0c;它会有提示成功或报错的信息。但是对比下来&#xff0c;我发现candence的编译器更加方便编写va&#xff0c;所以把运行成功的步骤记录下来,防止遗忘。 首先&#xff0c…

抖音机构号授权矩阵系统源码功能和优势全解析

抖音机构号授权矩阵系统源码是一款功能强大的工具&#xff0c;可以帮助机构号实现更高效的内容创作和发布。以下是该系统的功能和优势&#xff1a; 智能创作&#xff1a;系统内置AI技术&#xff0c;可以自动生成创作文案&#xff0c;大大减少了创作者的文案撰写时间和精力成本。…

决策树算法简单介绍:原理和方案实施

决策树算法介绍&#xff1a;原理和方案实施 决策树&#xff08;Decision Tree&#xff09;是一种常用的机器学习算法&#xff0c;它既可以用于分类任务&#xff0c;也可以用于回归任务。由于其直观性和解释性&#xff0c;决策树在数据分析和模型构建中得到了广泛的应用。本文将…

kubernetes集群如何更改所有节点IP

kubernetes集群如何更改所有节点IP 情景描述更换IP前的准备工作更换IP后的工作--master更换IP后的工作--node节点重新部署之前那些服务 情景描述 我有三台服务器&#xff0c;想要将其组成了一个kubernetes集群&#xff0c;在部署之前&#xff0c;我就对其进行了固定IP的操作&a…

使用OpencvSharp实现人脸识别

在网上有很多关于这方面的博客&#xff0c;但是都没有说完整&#xff0c;按照他们的博客做下来代码都不能跑。所以我就自己写个博客补充一下 我这使用的.NET框架版本是 .NetFramework4.7.1 使用Nuget安装这两个程序包就够了&#xff0c;不需要其他的配置 一定要安装OpenCvSha…

不可不看,年轻人必须丢掉的10条幼稚职场心理

点击上方△腾阳 关注 转载请联系授权 你好&#xff0c;我是腾阳。 初入职场&#xff0c;每位年轻人都怀揣着梦想与憧憬&#xff0c;但往往也伴随着一些“成长的烦恼”。 那些不经意间流露出的幼稚心理&#xff0c;如果不及时察觉并调整&#xff0c;可能会成为你职业道路上的…

记一次 Qt installer framework安装程序过程中 安装驱动依赖

在installscript.qs 文件中该函数添加exe 依赖程序放置位置

深视智能3d相机SDK例程开发模式和相机控制器类

一、二次开发介绍 为了应对客户的多种需求,我们提供了多种模式可供选择,客户可根据自己的需求灵活使用。本司提供的例程包含的模式包括一次回调模式,阻塞模式,无限循环模式,2.5D模式。 1.一次回调模式 一次回调模式:设定采集行数<=15000,一次性将采集的数据全部返回…

JVM:字节码文件

文章目录 一、Java虚拟机的组成二、字节码文件的组成1、基本信息2、常量池3、字段4、方法5、属性 三、常用的字节码工具1、javap -v 命令2、jclasslib插件3、阿里arthas 一、Java虚拟机的组成 二、字节码文件的组成 1、基本信息 魔数、字节码文件对应的Java版本号访问标识&am…

STM32-按键及传感器模块

本内容是基于江协科技STM32视频整理而得。 1. 按键及传感器模块 1.1 按键简介 按键&#xff1a;常见的输入设备&#xff0c;按下导通&#xff0c;松手断开&#xff1b; 按键抖动&#xff1a;由于按键内部使用的是机械式弹簧片来进行通断的&#xff0c;所以在按下和松手的瞬间…

大模型性能测试报告

性能测试背景 满足大模型在初期1万用户的正常使用 性能测试名词解释 术语 释义 VU 并发用户数 RT 响应时间 TPS 吞吐量的一种&#xff0c;指每秒处理的事务数&#xff0c;每个事务可以是一个接口或者多个接口 QPS 吞吐量的一种,指每秒服务器处理的请求数量&#xff…

HTML5新增的input元素属性:placeholder、required、autofocus、min、max等

HTML5 大幅度地增加与改良了 input 元素的属性&#xff0c;可以简单地使用这些属性来实现 HTML5 之前需要使用 JavaScript 才能实现的许多功能。 下面将详细介绍这些新增的 input 元素的属性。 属性说明属性说明placeholder在输入框显示描述性或提示性文本list为文本框添加选…

摸鱼大数据——Spark SQL——Spark SQL函数定义一

Spark SQL函数定义 1、窗口函数 回顾之前学习过的窗口函数&#xff1a; 分析函数 over(partition by xxx order by xxx [asc|desc] [rows between xxx and xxx])​分析函数可以大致分成如下3类&#xff1a;1- 第一类: 聚合函数 sum() count() avg() max() min()2- 第二类: 排…

Python酷库之旅-第三方库Pandas(011)

目录 一、用法精讲 25、pandas.HDFStore.get函数 25-1、语法 25-2、参数 25-3、功能 25-4、返回值 25-5、说明 25-6、用法 25-6-1、数据准备 25-6-2、代码示例 25-6-3、结果输出 26、pandas.HDFStore.select函数 26-1、语法 26-2、参数 26-3、功能 26-4、返回值…

3D模型格式转换工具HOOPS Exchange如何访问产品制造信息(PMI)?

在当今的制造和设计领域&#xff0c;产品制造信息&#xff08;PMI&#xff09;在确保零件和产品满足精确规格方面发挥着至关重要的作用。PMI&#xff0c;特别是几何尺寸和公差&#xff08;GD&T&#xff09;&#xff0c;提供了制造过程中必须遵循的详细指导。 随着技术的进…

centos7停服之后换阿里云的源

原因&#xff1a; Centos7停止维护 CentOS 7 官方支持在2024年6月30日结束。如果您正在使用CentOS 7&#xff0c;建议迁移到另一个仍在维护的Linux发行版&#xff0c;如CentOS Stream、AlmaLinux、Rocky Linux或者转换到使用Debian或Ubuntu。国产的华为的&#xff1a;openEule…

数据恢复篇:如何从硬盘中恢复照片

如何从计算机硬盘恢复图片&#xff1f; 和所有电子和机械设备一样&#xff0c;硬盘也可能因任何原因而损坏。如果您系统的硬盘停止工作&#xff0c;或者您在启动系统时听到振动声&#xff0c;则硬盘可能已损坏。如果是这样&#xff0c;硬盘上的数据怎么办&#xff1f; 不要惊…

Python开源工具库使用之离线翻译软件Argos-Translate

文章目录 一、软件介绍二、软件使用2.1 命令行使用2.2 Python代码调用2.3 GUI使用 三、软件获取 一、软件介绍 Argos-Translate 是一款基于 OpenNMT&#xff08;Open source Neural Machine Translation&#xff09;的离线翻译库&#xff0c;不需要联网就可以实现翻译功能&…

唐山养老院哪家好---养老!用哪种方式更合适?

人生旅途中&#xff0c;每个阶段都伴随着不同的挑战和难题。老年阶段尤其如此&#xff0c;随着岁月的流逝&#xff0c;人的身体机能逐渐衰退&#xff0c;自理能力也会随之减弱。面对这些挑战&#xff0c;老人及其家属需要找到合适的方法来保证老年的生活质量。 居家养老 在中…

7月学术会议:7月可投的EI国际会议

随着科技的迅猛发展&#xff0c;学术交流与研讨成为了推动科研进步的重要途径。进入7月&#xff0c;众多高质量的EI国际会议纷纷拉开帷幕&#xff0c;为全球的科研工作者提供了一个展示研究成果、交流学术思想的平台。以下&#xff0c;我们将详细介绍一些在7月可投的EI国际会议…