当前位置: 首页 > article >正文

std::vector的模拟实现

目录

构造函数

无参构造

用n个val来初始化的拷贝构造

拷贝构造

用迭代器初始化

析构函数

reserve

resize

pushback

pop_back

迭代器及解引用

迭代器的实现

解引用[ ]

insert

erase

赋值拷贝

补充


vector底层也是顺序表,但是vector可以储存不同的类型包括自定义类型和内置类型,所以在实现vector的时候要用模板实现。vector的成员变量与string是不同的,vector的成员变量是3个迭代器,分别是指向顺序表起始位置的指针,指向最后一个数据下一个位置的指针以及指向尾部开辟的空间。

namespace cfl
{
	template<class T>
	class vector
	{
	typedef T* iterator;
	public:



	private:
		iterator _start;      //指向顺序表的开头
		iterator _finish;     //指向顺序表最后一个有效数据的下一个位置
		iterator _end_of_storage;	//指向最后一个开辟的空间的下一个位置
	};
}

构造函数

vector的构造函数有5个,我们先实现4个,有一个涉及到容器分配器在写容器篇博客时会实现。

无参构造

//无参构造
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}

用n个val来初始化的拷贝构造
 

//用n个val来初始化的拷贝构造
vector(size_t n, const T& val)
{
	_start = new T[n];
	for (int i = 0; i < n; i++)
		_start[i] = val;

	_finish = _start + n;
	_end_of_storage = _start + n;
}

拷贝构造

注意在进行拷贝构造的时候,不建议使用memcpy,因为在使用memcpy的时候内置类型不会出问题,但是对于自定义类型可能会出现浅拷贝,自定义类型中有指针就只是简单的拷贝其地址,导致两个vector指向同一块位置,就会调用两次析构函数。

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

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

//拷贝构造
vector(const vector& v)
{
	_start = new T[v.capacity()];

	//memcpy(_start, v._start, v.size()*sizeof(T));
	用memcpy,如果T是自定义类型可能会导致浅拷贝
	比如T是string,则指挥拷贝string的地址,导致两个vector指向一个位置

	//用for循环和赋值运算符重载来实现
	for (int i = 0; i < v.size(); i++)
	{
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

用迭代器初始化

 此处使用迭代器初始化为了方便直接调用push_back(在后面实现)。

//用迭代器初始化
template<class InputIterator>  
	//此处用一个函数迭代器,使得vector可以用各种类型初始化,能用int数组也能用string等
vector(InputIterator first, InputIterator last)
{
	//直接调用push_back让其去开辟空间
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

析构函数

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

reserve

用reserve进行空间的开辟,与拷贝构造同理,这里也不建议使用memcpy进行拷贝。

void reserve(size_t n)
{
	if (n > capacity())
	{
		iterator tmp = new T[n];
		int sz = size();
		for (int i = 0; i < sz; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

resize

resize传参时如果小于size(),就会对空间进行缩小,如果大于就会额外对额外的空间进行初始化。

void resize(size_t n,const T& val=T()) //如果没有传val就用编译器默认的初始化
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish != _end_of_storage)
		{
			*(_finish) = val;
			++_finish;
		}
	}
}

pushback

void push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*(_finish) = val;
	++_finish;
}

pop_back

尾删。

void pop_back()
{
	_finish--;
}

迭代器及解引用

迭代器的实现

对于迭代器的实现就是实现begin和end。

迭代器的实现也分为两种:非const版本和const版本。

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}

typedef const T* const_iterator;
const_iterator begin()const
{
	return _start;
}
const_iterator end()const
{
	return _finish;
}

解引用[ ]

//解引用运算符重载
T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}

insert

insert插入。insert传的pos是在_start和_finish之间的,所以在扩容后对导致_start指向新的空间了,但是pos位置还指向原位置,所以扩容后要对pos位置进行更新。

void insert(iterator pos, const T& val)
{
	assert(pos < _finish);
	if (_finish == _end_of_storage)
	{
		int num = pos - _start;  //如果想开辟空间,会导致_start指向新的位置但是pos还指向原处
		reserve(2 * capacity());
		pos = _start + num; //对pos位置进行更新
	}
	iterator end = _finish;
	while (end > pos)
	{
		*(end) = *(end - 1);
		--end;
	}
	*pos = val;
	++_finish;
}

insert后不能再使用pos这个迭代器了,因为他可能失效。 可以用一个返回值来返回新的pos位置。


erase

指定位置删除。

void erase(iterator pos)
{
	iterator cur = pos;

	while (cur + 1 < _finish)
	{
		*(cur) = *(cur + 1);
		++cur;
	}
	--_finish;
}

赋值拷贝

void swap(T& tmp)
{
	swap(this->_start, tmp._start);
	swap(this->_finish, tmp._finish);
	swap(this->_end_of_storage, tmp._end_of_storage);
}

//赋值拷贝
T& operator=(T tmp)
{
	swap(tmp);
}

此处赋值拷贝还是使用交换临时变量的方法,即能够将tmp拷贝构造的空间给this还能让tmp去销毁this的原本数据。


补充

一些vector不常用的成员函数。

1)rbegin,rend从后往前开始;

2)clear()清除vector中的数据,不清除空间;

3)vector<vector<T>>实现用vector模拟二维数组。


http://www.kler.cn/a/578715.html

相关文章:

  • Python----数据可视化(Seaborn二:绘图一)
  • vue管理系统常规布局思路,头部+菜单+主题(Container 布局容器)
  • 【编译器】VSCODE搭建ESP32-C3
  • C++【类和对象】
  • 第四届大数据、区块链与经济管理国际学术会议
  • Spring使用@Scheduled注解的参数详解
  • 基于ANTLR4的大数据SQL编辑器解析引擎实践|得物技术
  • Redis- 切片集群
  • LEETCODE:二叉树的层序遍历JAVA
  • android viewmodel如何使用
  • 用OpenCV写个视频播放器可还行?(C++版)
  • 靶场(四)---小白心得全流程分析
  • AIP-162 资源修订
  • Docker(认识且会基础操作)
  • yolov5训练自己数据集的全流程+踩过的坑
  • Linux 入门:常用命令速查手册
  • 【 <一> 炼丹初探:JavaWeb 的起源与基础】之 JSP 标签库:自定义标签的开发与应用
  • 2025开源SCA工具推荐 | 组件依赖包安全风险检测利器
  • 探索在直播中的面部吸引力预测新的基准和多模态方法
  • 服务远程调用(RPC)架构及原理