vector类模板内部实现
无论是在newcode刷题 还是在leetcode刷题 如果采用c++编译环境,功能函数的参数还是返回值均采用vector来进行存储数据
为什么用vector来存储?
究其原因,vector类似于一个数组,通过连续的存储空间来存放数据,对此,访问数据十分方便,但在实现插入删除时却会消耗更多的时间和空间
与其同时,vector底层实现是基于类模板实现,所以,可以存储的数据类型可以自定义 包括int float string char 甚至自定义的类,结构体均可,这也是底层类模板实现的好处
下面具体看一下其内部如何实现
-
构造函数(包括无参构造,不同参数类型重载构造函数)
vector():_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
}
vector(size_t n, const T& val = T())
:_start(new T[n])
,_finish(_start+n)
,_endofstorage(_start+n)
{
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
}
template <class it>
vector(it first, it last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
第一个构造函数为无参构造类型,不传入任何参数,通过初始化列表对私有成员进行初始化,由于成员变量均为迭代器(实质为地址,功能类似于指针),初始化为空指针
其中私有成员包括如下:
private:
iterator _start;
iterator _finish;
iterator _endofstorage;//空间的结尾
第二个构造函数,第一个参数为容器长度n,第二个参数为带默认参数的模板类型的值,根据给的长度n,列表初始化通过new开辟n大小的内存空间
第三个构造函数 传入的两个参数为定义为模板类型的迭代器,通过迭代器依次赋值操作
-
尾插/尾删
void reverse(size_t n)
{
if (n > capacity())
{
//保存有效元素个数
size_t sz = size();
//申请空间
T* tmp = new T[n];
//拷贝原有空间
if (_start)
{
memcpy(tmp, _start, sizeof(T)*size());
delete[]_start;
}
//更新
_start = tmp;
//_finish=_start+size();//由于start更新 导致size()出问题 finish丢失
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void push_back(const T& val)
{
//检查容量
if(_finish==_endofstorage)
{
size_t newcap = _endofstorage == nullptr ? 1 : 2 * capacity();
reverse(newcap);
}
//尾插
*_finish = val;
_finish++;
}
void pop_back()
{
_finish--;
}
由于vector在实现插入数据时,通过动态开辟空间进行扩容,保证有足够的空间进行插入,所以在插入前第一步要做的就是检查容量
扩容过程无论哪一个模板,基本一致:
- 申请新空间
- 拷贝原有数据到新空间
- 释放原有空间
- 更新(由于空间拷贝发生变化,成员指针的指向需要更新)
由于有指向尾部元素下一位置的_finish指针存在,尾插及尾删操作需要注意对尾部指针的操作
测试结果如下:
-
任意位置前插入
void insert(iterator pos, const T& val)
{
//检查位置
assert( pos<=end()&&pos>=begin());
//检查容量
if (_finish == _endofstorage)
{
//记录偏移量
size_t offset = pos - _start;
size_t newcap = _endofstorage == nullptr ? 1 : 2 * capacity();
reverse(newcap);//增容空间拷贝导致迭代器失效 pos位置丢失
pos = _start + offset;//增容之后 更新pos位置
}
//移动元素
iterator it = end();
while (it>pos)
{
*it = *(it - 1);
it--;
}
*pos = val;
//更新
_finish++;
}
该功能函数传入的位置参数并非一个整数值,而是传入一个迭代器类型,即为一个地址,但需要考虑迭代器是否失效的问题
插入过程中可能会进行增容,而增容进行空间拷贝,原有pos指向的地址失效,对此增容之后,需要更新pos位置,需要在一开始记录pos位置相对于起始位置start的偏移量,方便后续更新pos位置
此时步骤依然与原有插入步骤类似
- 检测位置
- 检查容量,考虑是否增容
- 元素移动(从后向前移动)
- pos位置插入元素
- 更新大小
测试结果如下:
-
任意位置删除
iterator erase(iterator pos)//返回删除后下一个元素位置
{
//检查位置
assert(pos < end() && pos >= begin());
//移动元素
iterator it = pos+1;
while (it <end())
{
*(it-1) = *it;
it++;
}
//更新
_finish--;
return pos;
}
删除与插入步骤类似,但不需要进行增容,只需要最后更新大小即可
测试结果如下:
-
resize的实现
void resize(rsize_t n,const T& val=T())
{
//n>cap
if (n > capacity())
{
reverse(n);
}
if (n > size())//需要填充
{
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
//n<cap
_finish = _start + n;
}
resize的实现考虑三种情况,简单的n<容量,更新尾指针即可 如果n>容量需要增容,同时需要填充,以传入字符进行依次填充
-
迭代器的实现
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
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];
}
-
打印模板函数实现
template <class T>
void printfvector(vector<T>& vec,const T& val)
{
typename vector<T>::iterator it = vec.begin();
while (it != vec.end())
{
cout << *it << " ";
*it = val;
it++;
}
}
template <class T>
void printfvector(vector<T>& vec)
{
typename vector<T>::const_iterator it = vec.begin();
while (it != vec.end())
{
cout << *it << " ";
//*it = val;
it++;
}
}