C++ —— 模拟实现vector和迭代器失效

目录

1.成员变量的设计

 2.迭代器以及接口设计

3.容量接口

4.reserve的设计

5.resize的设计

6.尾插尾删的设计

7.构造、析构函数

8.运算符重载

9.insert接口设计与迭代器失效

10.erase的设计和迭代器失效

11.双层深拷贝问题

12.完整代码

1.成员变量的设计

成员变量设计为三个迭代器,分别是:_start、_finish、_endofstorage。此设计原理参考STL源码。_start与_finish之间是存储有效元素的;_start与_endofstorage之间表容器的容量。

iterator _start;
iterator _finish;
iterator _endofstorage;

 2.迭代器以及接口设计

使用指针的方式实现迭代器,同时需要兼顾 const 对象和 const 迭代器。

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;
}

3.容量接口

size_t size()
{
	return _finish - _start;
}
size_t capacity()
{
	return _endofstorage - _start;
}
void clear()
{
	_finish = _start;
}
bool empty()
{
	return _finish == _start;
}

4.reserve的设计

reserve如果涉及到扩容,统一采用异地扩容的方式。当参数小于当前容量时不作处理。

void reserve(const size_t& n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		assert(tmp);		//检查是否成功开辟
		int oldsize = _finish - _start;

		if (_start)		//如果_start不为空需要拷贝数据
		{
			memcpy(tmp, _start, sizeof(T) * oldsize);        
			delete[] _start;		//释放旧空间
		}

		//异地扩容,更新迭代器
		_start = tmp;
		_finish = _start + oldsize;
		_endofstorage = _start + n;
	}
}

5.resize的设计

resize需要分情况讨论,如果接收的参数小于当前容量时,空间不进行变动;如果小于当前有效元素个数时,需要更新_finish迭代器达到删除的效果。同时需要注意resize有第二个参数(官方手册)。

void resize(const size_t n, T val = T())
{
	if (n > capacity) reserve(n);		//大于当前容量,扩容
	if (n > size())
	{
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else _finish = _start + n;
}

6.尾插尾删的设计

void push_back(const T& val)
{
	if (_finish == _endofstorage)		//是否需要扩容
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = val;
	++_finish;
}
void pop_back()
{
	assert(!empty());		//尾删容器不能为空
	--_finish;
}

7.构造、析构函数

这里模拟实现:默认构造函数、范围拷贝、指定容器大小、拷贝构造函数(现代写法)、析构函数。

vector()		//默认构造函数
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

template <class it>
vector(it first, it last)		//范围拷贝构造
	: _start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	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(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	vector<T> tmp(v.begin(), v.end());
	swap(tmp);
}

vector(size_t n, const T& val = T())		//指定容器大小构造
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}

}

~vector()		//析构函数
{
	delete[] _start;
	_start = _finish = _endofstorage = nullptr;
}

上面的代码会出一个错误:

其原因在于:10 在编译器的视角中默认是一个 int 类型的数,而我们的接口设计是一个 size_t 类型的数,所以会存在一些类型提升。而我们又设计了一个模板,在编译器的视角看来,调用模板函数是开销更小的选择。

解决方案是添加一个重载:

vector(int n, const T& val = T())		//指定容器大小构造
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

8.运算符重载

需不需要使用 const 函数是看我们需求来的,这里给出设计原则:

  • 只读接口函数,设计为const函数
  • 只写接口函数,设计为非const函数
  • 读、写接口函数,const函数、非const函数都要设计

上面的代码可能考虑的不够周全,读者可自行改进。下标运算符对于我们来说是可读可写的。

T& operator[](const size_t n)
{
	assert(n >= 0 && n < size());		//防止越界
	return _start[n];
}

const T& operator[](const size_t n) const
{
	assert(n >= 0 && n < size());		//防止越界
	return _start[n];
}

vector<T>& operator=(vector<T> v)		//赋值运算符重载
{
	swap(v);
	return *this;
}

9.insert接口设计与迭代器失效

void insert(iterator pos,const T& val)
{
	assert(pos >= _start && pos < _finish);
	if (_finish == _endofstorage)		//检查是否需要扩容
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	auto end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
}

这样设计的接口发生扩容时会产生迭代器失效,因为扩容是异地扩容,就会导致pos指向的空间已经被释放了,需要更新pos。因为是传值调用,我们还必须保证外部的迭代器能够及时更新。

iterator insert(iterator pos,const T& val)
{
	assert(pos >= _start && pos < _finish);
	if (_finish == _endofstorage)		//检查是否需要扩容
	{
		size_t oldsize = _finish - _start;
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
		pos = _start + oldsize;		//扩容后,_start的指向已经更改,需要更新pos
	}
	auto end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
	return pos;		//添加一个返回值供外部迭代器更新
}

外部需要继续使用迭代器的正确方法为:

在VS环境中使用标准库的insert接口,不按上面的方式使用迭代器,就会发生迭代器失效问题。

10.erase的设计和迭代器失效

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

这样做会导致迭代器失效。其原因在于:删除最后一个元素,del的位置会指向一个不合法的位置,如果后续要对这个指针指向的内容进行读、写是非常危险的。所以与insert一样,VS的编译环境是使用insert和erase后迭代器失效。解决方案是返回指向删除元素位置的迭代器。

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

外部调用时正确的使用方法:

使用标准库中的vector容器,不按上述方法使用会发生错误:

11.双层深拷贝问题

留意刚才的reserve接口:

void reserve(const size_t& n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		assert(tmp);		//检查是否成功开辟
		int oldsize = _finish - _start;

		if (_start)		//如果_start不为空需要拷贝数据
		{
			memcpy(tmp, _start, sizeof(T) * oldsize);
			delete[] _start;		//释放旧空间
		}

		//异地扩容,更新迭代器
		_start = tmp;
		_finish = _start + oldsize;
		_endofstorage = _start + n;
	}
}

这么做会导致一个非常严重的野指针问题:

其原因在于:异地扩容之后,使用memcpy进行拷贝,而这个拷贝是一次浅拷贝,新空间和旧空间的对象都指向原来的_start指向的空间,当_start指向的空间释放后,新空间的对象_start就是野指针了,所以打印的就是乱码。当函数栈帧退出时又析构一次,就会造成一次严重的野指针问题。 

所以我们的解决方案是不使用memcpy进行浅拷贝,而是使用赋值运算符重载进行深拷贝。

void reserve(const size_t n)
{
	if (n > capacity())
	{
		int oldsize = _finish - _start;
		T* tmp = new T[n];
		assert(tmp);		//检查是否成功开辟

		if (_start)		//如果_start不为空需要拷贝数据
		{
			//memcpy(tmp, _start, sizeof(T) * oldsize);
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];		//使用赋值运算符重载
			}
			delete[] _start;		//释放旧空间
		}

		//异地扩容,更新迭代器
		_start = tmp;
		_finish = _start + oldsize;
		_endofstorage = _start + n;
	}
}

12.完整代码

#include <assert.h>
#include <string.h>
#include <algorithm>

namespace ly
{
	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()		//默认构造函数
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

		~vector()		//析构函数
		{
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}


		template <class it>
		vector(it first, it last)		//范围拷贝构造
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			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(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

		vector(size_t n, const T& val = T())		//指定容器大小构造
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}

		}
		vector(int n, const T& val = T())		//指定容器大小构造
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}

		}
		
		T& operator[](const size_t n)
		{
			assert(n < size());		//防止越界
			return _start[n];
		}
		const T& operator[](const size_t n) const
		{
			assert( n < size());		//防止越界
			return _start[n];
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

void reserve(const size_t n)
{
	if (n > capacity())
	{
		int oldsize = _finish - _start;
		T* tmp = new T[n];
		assert(tmp);		//检查是否成功开辟

		if (_start)		//如果_start不为空需要拷贝数据
		{
			//memcpy(tmp, _start, sizeof(T) * oldsize);
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];		//使用运算符重载
			}
			delete[] _start;		//释放旧空间
		}

		//异地扩容,更新迭代器
		_start = tmp;
		_finish = _start + oldsize;
		_endofstorage = _start + n;
	}
}


		void resize(const size_t n, T val = T())
		{
			if (n > capacity) reserve(n);		//大于当前容量,扩容
			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else _finish = _start + n;
		}

		size_t size()
		{
			return _finish - _start;
		}
		size_t capacity()
		{
			return _endofstorage - _start;
		}
		void clear()
		{
			_finish = _start;
		}
		bool empty()
		{
			return _finish == _start;
		}

		void push_back(const T& val)
		{
			if (_finish == _endofstorage)		//是否需要扩容
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = val;
			++_finish;
		}
		void pop_back()
		{
			assert(!empty());		//尾删容器不能为空
			--_finish;
		}

		iterator insert(iterator pos,const T& val)
		{
			assert(pos >= _start && pos < _finish);
			if (_finish == _endofstorage)		//检查是否需要扩容
			{
				size_t oldsize = _finish - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + oldsize;		//扩容后,_start的指向已经更改,需要更新pos
			}
			auto end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;
			return pos;		//添加一个返回值供外部迭代器更新
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			
			auto del = pos + 1;
			while (del < _finish)
			{
				*(del - 1) = *del;
				++del;
			}
			--_finish;
			return pos;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;

	};
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小龙向钱进

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值