STL中vector模拟实现

vector各个接口函数

//构造函数
vector()
vector(size_t n,const T& val=T())
vector(int n,const T& val = T())
//拷贝构造函数
vector(const vector<T>& v)
//迭代器版本的
vector(inputiterator first, inputiterator end)
//赋值运算符重载
vector<T>& operator=( vector<T> v)
//析构函数
~vector()
//容量相关的函数
void reserve(size_t n)//扩容版本
void resize(size_t n, T val = T())//扩容+初始化版本
size_t size()const//数量
size_t capacity() const容量
//插入修改容器中的数据的函数
void push_back(T x)
iterator insert(iterator pos, const T& val)
iterator erase(iterator pos)
迭代器相关的函数
iterator begin()//指向首元素
iterator end()//指向最后一个元素的下一个位置
const_iterator begin()const 
const_iterator end()const
//容器中数据访问的[]
T& operator[](size_t pos)
const T& operator[](size_t pos)const

vector成员中相关成员变量

vector本质上就是一个动态开辟的数组,里面的成员跟以前的顺序表没啥本质区别,但是vector是用模板实现的,可以任意类型的数据。我们也可以看看源码中vector是怎么实现的呢?
STL中vector模拟实现_第1张图片

分别使用了一个迭代器的指针,一个表示首元素,一个表示最后一个元素的下一个位置,还有一个就是end_of_storage表示容量。我在下面的写的vector当中给了缺省值,避免每次写构造函数的时候都要初始化一下。

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

默认成员函数

我给了缺省值,会自动走初始化列表,因此我在这边不要在初始化一下了。

vector()
{}

构造函数的实现
vector支持一种这样的构造,你可以给出n个你想初始化的值,在l类与对象那么我们知道构造函数对内置类型不做处理,对自定义类型会去调用它的构造函数。但是C++11以后对内置类型也会做处理了。int x= int(); char c=char();

vector(size_t n,const T& val=T())//c++11之后支持对内置类型也会初始化
{
	if (size() + n > capacity())
	{
		reserve(size() + n);
	}
	while ((int)n--)//会发生隐式类型的转化
	{
		push_back(val);
	}
	_finish += n;
	
}

我上面还写了一个int类型的构造这边就不放了,主要是模板会自动匹配它的最优选择,我们一般给vector v(10,1);会自动隐式类型转换成int int 的类型。

拷贝构造函数

写法思路就是_start先开辟一块与该容器大小相等的空间,然后将该容器的数据一个一个的拷贝过来,最后改变一下_finish跟_end_of_storage

vector(const vector<T>& v)
{
	std::cout << "vector(const T& v)" << std::endl;
	_start = new T[v.capacity()];
	//memcpy(_start, v._start, v.size()*sizeof(T));
	for (size_t i = 0; i < v.size(); i++)
	{
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

赋值运算符重载的函数

传统写法
思路就是开辟一个与容器大小相同的tmp空间,然后将容器中数据传给tmp数组,思路跟拷贝构造后面一致。

//传统写法
vector<T>& operator=(vector<T>& v)
{
	if (this != &v)
	{
		iterator tmp = new T[v.capacity()];
		for (size_t i = 0; i < v.size(); i++)
		{
			tmp[i] = v._start[i];
		}
		_start = tmp;
		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
	}
	return *this;
}

现代写法

就是通过算法库里面的swap交换一下俩者的指针所指向的地址

void Swap(vector<T>& v)
{
	std::swap(_start,v._start);
	std::swap(_finish,v._finish);
	std::swap(_end_of_storage,v._end_of_storage);
}
	vector<T>& operator=( vector<T> v)
	{
		Swap(v);
	   return *this;
	}

迭代器版本的构造函数
vector还支持使用一段迭代器区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板,在函数体内将该迭代器区间的数据一个个尾插到容器当中即可。

template<class inputiterator>
//迭代器版本默认是左闭右开的
vector(inputiterator first, inputiterator end)
{
	while (first != end)
	{
		push_back(*first);
		first++;
	}
}

析构函数

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

容量相关的函数

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

reserve的实现规则就是判断当前的是否达到最大的容积,达到就扩容,开辟一块tmp空间,当_start不为空的时候就开始挪动数据。

void reserve(size_t n)
{
	if (n > capacity())
	{
		iterator tmp = new T[n];
		//挪动数据
		if (_start)
		{
			size_t sz = size();
			//memcpy(tmp, _start, sizeof(T) * sz);//浅拷贝
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];//实现深拷贝
			}
		}
		delete[] _start;
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

resize函数实现的思路就是当输入n小于当前内存的时候我们就不需要初始化,并且跟新一下_finish=_start+n,,当大于的时候就需要分俩种情况,一种是当vector为空的时候,我们就直接在头部开始插入数据,第二种就是本来就有数据,需要尾插数据一直达到n个大小。

void resize(size_t n, T val = T())
{
	if (n <=size())
	{
		_finish =_start+n;
	}
	else
	{
		reserve(n);
		while ((int)n--)
		{
			if (_start)
			{
				push_back(val);
			}
			else
			{
				insert(begin(), val);
			}
		}
	}
}

插入修改容器中的数据的函数

尾插数据push_back,实现思路就是插入数据首先就是扩容,然后在finish后面插入数据,然后让finish++一下。

void push_back(T x)
{
	if (_finish >= _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = x;
	_finish++;
}

insert()的写法,以前的写法就是给一个下标pos的位置,然后删除对应pos位置上的数据,而现在这个pos变成了指针,我们在使用指针插入对应数据的时候,特别容易出现迭代器失效的情况,也就是野指针的行为,为啥呢?因此我们在插入数据的时候需要扩容,我们是额外开了一个数组,然后就会将原数组释放掉,再让_start指向新开辟的数组,所以我们需要保存pos指针的位置。

iterator insert(iterator pos, const T& val)
{
	assert(pos <=_finish);
	size_t len = pos - _start;
	if (_finish >= _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	iterator end = _finish - 1;
	pos = _start + len;//跟新pos指针的位置
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
	return pos;
}

erase的实现,在Linux跟vs中对erase()底层实现都有区别,vs中只要删除了该数据,该位置的值就不可访问或者解引用修改查看,而inux环境下就宽松了很多,我们可以随意的访问或者修改,但是出错了也意味着不容易判断。

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

迭代器相关的函数和数据访问

这个很简单直接放代码

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin()const 
{
	return _start;
}
const_iterator end()const
{
	return _finish;
}
T& operator[](size_t pos)
{
	assert(pos <= size());
	return _start[pos];
}
const T& operator[](size_t pos)const
{
	assert(pos <= size());
	return _start[pos];
}

你可能感兴趣的:(c++学习,c++,java,android)