模拟实现vector(insert与erase迭代器失效)

这是STL源码剖析中对于vector逻辑结构的一张图。
在这里插入图片描述

和之前的顺序表不一样,成员变量我们采用的是一个个指针,start指向数组的首元素,finish指向末尾元素的下一个。endofstage指向数组容量的下一个。扩容Linux下是两倍,vs下是一倍。

1. 迭代器

数组的迭代器是一个原生指针,直接typedef即可,但是注意要重命名两个版本,一个为普通迭代器,一个为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;
		}

2. 构造函数

2.1 无参构造

无参构造函数,全部初始化为null,插入的时候内部有容量检查,会自动扩容

		vector()
			:_start(nullptr)
			,_finish(nullptr)
			, _end_of_stage(nullptr)
		{}

2.2 迭代器构造

根据迭代器区间构造,只要是一段迭代器都可以来构造,比如这是一个vector<int>对象,
则可以任意的list<int>来构造他。所以将它写成模板。
内部开成和构造你迭代器一样大的空间,将解引用取得数据一步步尾插进vector。

template <class Inputiterator>
		vector(Inputiterator first,Inputiterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_stage(nullptr)
		{
			reserve(last - first);
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

2.3 拷贝构造

用形参的对象,迭代器构造一个临时对象,然后交换两个对象的成员变量即可(不用直接交换两个对象,自己实现一个交换数据就好)

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

这个临时对象出作用域,自己就把null析构了(析构null是不会报错的)

		//拷贝构造,现代写法
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_stage(nullptr)
		{
			vector<T> temp(v.begin(), v.end());
			swap(temp);
		}

2.4 赋值重载

这里的形参没有传递引用,形参是外面对象的一份拷贝,由于是赋值重载肯定是两个对象都存在,所以这里不用对被赋值对象初始化,直接和赋值对象交换数据,完全不会影响外面的对象。最后返回即可。而且由于形参是外面对象的拷贝,出作用域析构被赋值对象原来的数据。

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

3. 析构函数

析构函数,资源清理工作,需要释放里面申请的资源。必需要用delete(先析构释放资源后free(_start)),万一数组里面存的是一个个自定义类型则需要调用他们的析构函数。
在这里插入图片描述

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

4. 有效元素的个数

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

5. 容量大小

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

6. 重载[]

[]时vector最经常使用的一个函数。

		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t ops)const
		{
			assert(pos < size());
			return _start[pos];
		}

7. 检查容量

当指向有效元素的指针和指向容量的指针相遇,代表要扩容了。首先假如开始什么都没有,链各个指针为NULL,相减为0,那么就给他初始的4个容量,假如不是开始为0,那么就给他原来容量的2倍。

		void check_capacity()
		{
			if (_end_of_stage == _finish)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
		}

8. 扩容

当要给的容量大于当前容量,进行扩容,首先申请一段空间(4个空间或者传进来,乘以2倍的参数),把原来空间的元素拷贝过来,memcpy是很方便,但是他是值拷贝,对于内置类型,没区别,假如里面有自定义类型,只是拷贝了地址。
在这里插入图片描述
用for循环,将他们的值一个个赋值进当前空间,delete掉旧空间,让_start指向新空间。因为虽然我们的容量变大了,_start指向新空间之前先把原空间size记录下来,因为有效元素的个数没有变化,你在_start指向之后再算size,_finish还在指向原来的空间,_finish-_start,简直就是在乱减。所以提前记录下size,_finish=_start+oldsize._finish指向了我们新申请空间的有效元素的下一个位置。容量大小就是_start+传进来的扩容大小参数。
在这里插入图片描述

在这里插入图片描述

		//扩容
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* temp = new T[n];

				//memcpy(temp,_start,sizeof(T)*size());memcpy为浅拷贝。vector<int>没有问题,vector<string>析构的时候会报错
				for (size_t i = 0; i < size(); i++)
				{
					temp[i] = _start[i];
				}
				size_t oldsize = size();
				delete[] _start;
				
				_start = temp;
				_finish = _start + oldsize;
				//_finish = _start + size();此时不能调用size(),finsh还在指向那块释放的空间
				_end_of_stage = _start + n;
			}
		}

9. resize

先判断够不够,不够然后扩容。假如n<size(),就说明要缩小空间。假如n>size,就把x赋值到后面。

		//扩容+初始化,缩小size
		//resize给了个缺省值,因为是匿名对象,所以要带const
		void resize(size_t n, const T& x=T())
		{
			//假如不够就会扩容
			if (n > capacity())
			{
				reserve(n);
			}
			//假如n<size(),就缩小空间
			if (n < size())
			{
				_finish = _start + n;
			}
			else{

			   //假如n>size()就把x赋值进去
				while (_finish != _start + n)
				{
					*_finish = x;
					_finish++;
				}
			}
			
		}

10. 插入

find找到pos,然后传进来。在此位置插入x对象。assert的意思就是不能大于finish,不能小于_start。
提前记录下pos与_start的距离,由于扩容会引起重开一段空间
在这里插入图片描述

在来看插入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
虽然在函数里面我们处理了,但是不要忘记了pos是外面find得来的,而你是传值里面处理了,出作用域,不影响外面,迭代器就会失效,这里用返回值来解决了这个问题(即函数里面处理返回,外面接收)。其实不接收并不一定失效,因为不一定会增容。

	//参数是迭代器,配合find使用
		iterator insert(iterator pos, const T& x)
		{
			assert(pos<=_finish && pos>=_start);
			//假如扩容会引起迭代器失效,增容重开一段空间,导致pos还在指向释放的空间
			//提前记录posi的值,扩容的话让_start+posi就又找到了pos的位置
			size_t posi = pos - _start;
			check_capacity();
			
			pos = _start + posi;
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = x;
			++_finish;
			return pos;
		}

11. erase

在这里插入图片描述

//返回的pos的下一个位置(挪前来了)
		iterator erase(iterator pos)
		{
			assert(pos < _finish && pos >= _start);
			iterator it=pos+1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
		}

erase一定会导致失效,因为意义变了也算是失效的一种,原本指向5,现在指向1,所以返回值返回1,在外面接收,赋予pos新的意义(即此时pos代表1)
在这里插入图片描述
此时pos代表3,由于只是–finish,所以pos依旧指向3,但是此时3已经无意义。通过返回值返回告诉外面pos以无意义,当再次使用它时由于assert(pos<finish)就不会进入函数。
在这里插入图片描述
对于erase更多的是意义变了导致的失效,所以当在检查严格的编译器运行会导致崩溃。
例如一个删除偶数场景
在这里插入图片描述

所以当erase一个数据的时候都要当成失效,要用返回值来接收在函数里修改之后的pos。

插入与删除迭代器失效总结

insert不一定会失效,因为不是每次插入都会扩容,那假如会扩容呢,函数内部通过记录位置处理,因为传值外部不会改变,调用时统一用返回值接收来预防迭代器失效的场景。

erase一定会失效,因为意义变了也是失效的一种,会引起各种各样的错误,同样通过返回值来解决。

vector实现代码

#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<string>
namespace zjn
{
	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;
		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_stage, v._end_of_stage);
		}
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			, _end_of_stage(nullptr)
		{}

		template <class Inputiterator>
		vector(Inputiterator first,Inputiterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_stage(nullptr)
		{
			reserve(last - first);
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		//拷贝构造,现代写法
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_stage(nullptr)
		{
			vector<T> temp(v.begin(), v.end());
			swap(temp);
		}
		//赋值重载,现代写法
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_stage=nullptr;
		}
	
		
		const size_t size()const
		{
			return _finish - _start;
		}
		const size_t capacity()const
		{
			return _end_of_stage - _start;
		}
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t ops)const
		{
			assert(pos < size());
			return _start[pos];
		}
		//扩容
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* temp = new T[n];

				//memcpy(temp,_start,sizeof(T)*size());memcpy为浅拷贝。vector<int>没有问题,vector<string>析构的时候会报错
				for (size_t i = 0; i < size(); i++)
				{
					temp[i] = _start[i];
				}
				size_t oldsize = size();
				delete[] _start;
				
				_start = temp;
				_finish = _start + oldsize;
				//_finish = _start + size();//此时不能调用size(),finsh还在指向那块释放的空间
				_end_of_stage = _start + n;
			}
		}
		//扩容+初始化,缩小size
		//resize给了个缺省值,因为是匿名对象,所以要带const
		void resize(size_t n, const T& x=T())
		{
			//假如不够就会扩容
			if (n > capacity())
			{
				reserve(n);
			}
			//假如n<size(),就缩小空间
			if (n < size())
			{
				_finish = _start + n;
			}
			else{

			   //假如n>size()就把x赋值进去
				while (_finish != _start + n)
				{
					*_finish = x;
					_finish++;
				}
			}
			
		}
		void check_capacity()
		{
			if (_end_of_stage == _finish)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
		}
		void push_back(const T& x)
		{
			//check_capacity();
			//*_finish= x;
			//_finish++;
			insert(_finish,x);
		}
		void pop_back()
		{
			
			erase(_finish-1);
			
		}
		//参数是迭代器,配合find使用
		iterator insert(iterator pos, const T& x)
		{
			assert(pos<=_finish && pos>=_start);
			//假如扩容会引起迭代器失效,增容重开一段空间,导致pos还在指向释放的空间
			//提前记录posi的值,扩容的话让_start+posi就又找到了pos的位置
			size_t posi = pos - _start;
			check_capacity();
			
			pos = _start + posi;
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = x;
			++_finish;
			return pos;
		}
		//返回的pos的下一个位置(挪前来了)
		iterator erase(iterator pos)
		{
			assert(pos < _finish && pos >= _start);
			iterator it=pos+1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_stage;

	};

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楠c

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

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

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

打赏作者

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

抵扣说明:

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

余额充值