首页 > 其他分享 >vector模拟实现

vector模拟实现

时间:2024-06-12 23:00:38浏览次数:14  
标签:finish iterator 实现 pos start vector 模拟 size

目录

vector介绍

vector示意图 

关于vector扩容的问题

vector框架

构造函数

析构函数 

vector有关空间容量函数

insert和erase

pop_back和push_back

其它构造函数

拷贝构造

迭代器区间构造

 运算符重载

关于迭代器失效问题【重点】

有关insert发生迭代器失效

有关erase发生迭代器失效

vector模拟实现整体代码


vector介绍
1. vector 是表示可变大小数组的序列容器,就像数组一样,vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。 2. 本质讲, vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小。 3. vector 分配空间策略: vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。 4. 与其它动态序列容器相比( deque, list and forward_list ), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list 和 forward_list统一的迭代器和引用更好
vector示意图 

上面的图片来自于《STL源码剖析》这本书,从图中我们就可以看出vector实现的基本形式。vector类的成员变量是三个指针:指向起始位置的 _start ,指向最后一个数据的下一个位置的_finish,指向容量的下一个位置的_end_of_storage。 

关于vector扩容的问题

使用以下一段demo进行测试:

void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

在VS2022下:(大致为1.5倍)

在gcc下:(2倍)

vector框架
template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _endofstorage = nullptr;
};
构造函数
vector()
	:_start(nullptr)
    , _finish(nullptr)
	, _end_of_storage(nullptr)
{}
析构函数 
~vector()
{
	delete[] _start;
	_start = _finish = _endofstorage = nullptr;
}
vector有关空间容量函数
size_t capacity() const
{
	return _endofstorage - _start;
}

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

bool empty()const
{
	return size() == 0;
}

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		size_t sz = size();

		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * sz);
			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);
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

需要注意的是:关于reserve函数,当数值大于当前vector的容量时,需要进行拷贝,使用memcpy进行拷贝时(浅拷贝),对于内置类型不会有问题, 但是对于自定义类型就会有问题,比如string,

会引发扩容,就会引发对旧内容进行拷贝,如果使用memcpy进行拷贝,会出问题:

问题如下:

拷贝完以后,浅拷贝回来对应指针,当旧对象释放后,新对象指向的内容就无意义了,这就是问题所在,解决办法如上面reserve函数。

insert和erase
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = x;
	++_finish;
}

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;

	return pos;
}
pop_back和push_back
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x;
	_finish++;
}

void pop_back()
{
	assert(_finish > _start);
	_finish--;
}
其它构造函数
拷贝构造
vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

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;
	}
}
 运算符重载
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

vector<T>& operator=(vector<T> tmp)
{
	swap(tmp);
	return *this;
}

T& operator[](size_t pos)
{
	assert(pos < size());

	return _start[pos];
}

const T& operator[](size_t pos) const
{
	assert(pos < size());

	return _start[pos];
}
关于迭代器失效问题【重点】
有关insert发生迭代器失效

迭代器失效原因:

insert时迭代器失效发生在insert前size=capacity,当insert时,会先扩容,但是扩容后,pos还是指向原来的空间的位置,且已经被释放,那么当再次使用pos时,就会导致程序崩溃!!!

解决办法:更新pos位置迭代器,使其指向扩容后对应的位置,并返回pos位置迭代器。

iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = x;
	++_finish;
	return pos;
}

通过查阅文档也可以发现,官方的做法也是返回Insert位置的迭代器:

有关erase发生迭代器失效

erase函数错误写法:

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

演示代码:

运行结果:

出错原因:

it永远不会等于finish

解决办法:返回pos位置迭代器

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;

	return pos;
}

对应示例

void test()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    
    //删除所有偶数
    auto it = v.begin();
    while(it != v.end())
    {
        if(*it % 2 == 0)
        {
            it = v.erase(*it);
        }
        else
        {
            it++;
        }
    }
 
    for(auto e : v)
    {
        cout << e << " ";
    }
}

 通过查阅文档也可以发现,官方的做法也是返回erase位置的迭代器:

vector模拟实现整体代码
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()
	{}

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

	vector(size_t n, const T& val = T())
	{
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

	vector(int n, const T& val = T())
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

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

	void swap(vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_endofstorage, v._endofstorage);
	}

	vector<T>& operator=(vector<T> tmp)
	{
		swap(tmp);
		return *this;
	}

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

	void reserve(size_t n)
	{
		if (n > capacity())
		{
			T* tmp = new T[n];
			size_t sz = size();

			if (_start)
			{
				//memcpy(tmp, _start, sizeof(T) * sz);
				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);
			while (_finish < _start + n)
			{
				*_finish = val;
				++_finish;
			}
		}
	}

	void push_back(const T& x)
	{/*
		if (_finish == _endofstorage)
		{
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}

		*_finish = x;
		++_finish;*/

		insert(end(), x);
	}

	iterator insert(iterator pos, const T& x)
	{
		assert(pos >= _start);
		assert(pos <= _finish);

		if (_finish == _endofstorage)
		{
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			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 + 1;
		while (it < _finish)
		{
			*(it - 1) = *it;
			++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];
	}

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

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

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

标签:finish,iterator,实现,pos,start,vector,模拟,size
From: https://blog.csdn.net/wmh_1234567/article/details/139577442

相关文章

  • 树结构的实现
    树的概念        树是一种非线性的数据结构,它是由n个有限节点组成一个具有层次关系的集合,它看起来像棵树,所以称其为“树”。如下图:     树可以分为根和子树,而子树又可以被分为根和子树,故我们可以用递归对其进行实现。注意:子树之间不能相交树的实现1.顺序......
  • c++哈希表hash_table的深度学习(hash_map,un和hash_set的底层实现)
    什么是哈希表?哈希表(HashTable)是一种数据结构,它使用哈希函数将键(key)映射到桶(bucket)或槽(slot)中,可以直接通过相应的键值直接对数据进行访问,高效的插入,删除,查找 哈希表的组成部分和特性哈希函数:哈希函数接受一个键作为输入,并返回一个索引值(通常是一个整数),该索引值用于确定键......
  • Python列表和元组的底层实现
    引言在Python编程中,列表(List)和元组(Tuple)是两种非常常用的数据结构。它们都用于存储序列数据,但列表是可变的,而元组是不可变的。本文将深入探讨Python列表和元组的底层实现原理,帮助你更好地理解它们的行为和性能特点。1.列表的底层实现列表在Python中是通过数组实现的。数......
  • 基于STM32单片机的无线智能窗户报警系统的设计与实现
    目录前言 一、设计任务 二、系统硬件设计1.元器件选用2.Android功能界面展示三、系统程序流程设计前言为解决传统智能家居在使用过程中缺少的人机交互功能、数据不可见、缺少控制、无法智能化处理事件等问题。因此,本文设计了以STM32单片机为核心的无线智能窗户报警......
  • 【BUG】鸿蒙模拟器虚拟化问题的解决方案
    找的大佬的教程,有些区别记录在此,以备忘记:创建记事本文档.txt,键入以下代码:pushd"%~dp0"dir/b%SystemRoot%\servicing\Packages\*Hyper-V*.mum>hyper-v.txtfor/f%%iin('findstr/i.hyper-v.txt2^>nul')dodism/online/norestart/add-package:"%System......
  • 基于python的旅游综合平台实现
    博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实实在在的写点程序。......
  • 【ARM Coresight Debug 系列 -- ARMv8/v9 Watchpoint 软件实现地址监控详细介绍】
    请阅读【嵌入式开发学习必备专栏】文章目录ARMv8/v9WatchpointexceptionsWatchpoint配置信息读取ExecutionconditionsWatchpointdataaddresscomparisonsSizeofthedataaccessWatchpoint软件配置流程WatchpointType使用介绍WT,Bit[20]:WatchpointType......
  • 使用Wesky.Net.OpenTools包来快速实现嵌套型结构体数据转换功能
    今天遇到有人提到结构体和byte数组互转的问题,我就顺便拿来水一篇。这是一个冷门的问题,估计使用的人不多。既然有需求,应该就有使用场景,那就顺便整一波。为了达到效果,结构体、复杂结构体嵌套等都能实现转换,我就顺便做了个包更新来提供使用和下面的说明。首先引入nuget包Wesky.Net......
  • 使用B树实现员工(人事)管理系统
    1.前言 使用B树来表示人事管理系统,其中每个节点代表一个人员,树的根节点为董事长,每个节点可以有多个子节点,表示下属。每一层代表一个等级分布。addPerson:添加人员功能通过查找指定上司节点,然后将新的人员作为其子节点添加。deletePerson:删除人员功能首先查找要删除人员......
  • 1. Two Sum Go实现
    在数组中找到2个数之和等于给定值的数字,结果返回2个数字在数组中的下标。1.解法1时间复杂度O(n^2)直接两次遍历所有节点,进行求和比较代码如下:functwoSum(nums[]int,targetint)[]int{ res:=make([]int,2,2) fori:=0;i<len(nums);i++{ forj:=i+1;j<len......