vector的模拟实现
在上一篇的博客中,我详细介绍了vector的使用,为了能够更好的使用vector,还需要仔细了解它的底层原理。
vector内部的成员有三个:
成员 | 标志 |
---|---|
_start | 表示已用空间的起始位置 |
_finish | 表示已用空间的终止位置 |
_end_of_storage | 表示可用空间的终止位置 |
迭代器
vector在存储方式上与数组是一样的,都是使用连续空间进行存储的,与数组不同的是它是动态的,因此vector的迭代器必须支持随机访问。正因为是连续空间的存储方式,原生指针就可以作为vector的迭代器。
//实现迭代器
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
构造函数
构造函数有很多种:
无参构造函数
无参构造,说明vector中所有的指针都是空指针,vector内部没有分配空间。那么可以通过初始化vector中的三个成员为空指针来进行构造。
vector()
: _start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{}
带长度和初始化值的构造函数
在创建时在vector放入n个初始化值,也就是在开始的时候对vector的空间和内容进行初始化。
vector(int n, const T& val = T())//这里的第二个值为初始化值,缺省值相当于一个匿名对象,也就是你是什么类型,就初始化什么
: _start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
reserve(n);
//将要初始化的值放进去
//可以通过指针来实现,也可以通过push_back实现
while (n--)
{
//push_back(val);
*(_start + n) = val;
_finish++;
}
}
在实现的时候为了防止代码冗余,在修改空间的时候复用了reserve函数(reserve函数的实现在下面介绍),对内容的初始化遍历赋值或者复用push_back函数。
使用迭代器的构造函数
由于vector是通过连续空间进行存储的,那么它的迭代器可以用原生指针来代替,具有随机访问的功能。那么通过迭代器来进行初始化就是遍历这一段空间,将该段空间具有的值放入。
//3、通过迭代器初始化
//使用函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
reserve(last - first);
while (first != last)
{
//push_back(*first);
first++;
}
}
使用函数模板的原因是,迭代器的种类也有很多,那么如果只写一个单一的函数,遇到其他的迭代器还得重新再写一个,这个就可以通过函数模板解决。
拷贝构造函数
拷贝构造函数就是对于给定的一个vector,构造一个与之相同的vector。为了减少代码的冗余,尽量使用已经写好的函数复用。那么我们的思路可以是这样的:要拷贝构造一个与给定的vector,首先内部空间一定是相同的,那么可以用reserve函数开辟与给定的vector相同的空间;然后对内部的元素进行遍历拷贝。
//拷贝构造函数
//下面这种开辟空间以及成员变量的修改操作是在reserve函数中进行的
//这块存在一点问题:正常来说,如果拷贝构造不进行初始化的话,在reserve时不是空指针就会对空间进行释放,此时就会报错
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
reserve(v.capacity());
//此处同理也不能用memcpy进行拷贝
//如果命名为cbegin,cend就会出错
for (auto e : v)
push_back(e);
}
这里面有几处细节需要注意:
1、在用reserve函数进行拷贝构造的时候,需要对成员变量进行空指针初始化,这是由于调用的是reserve函数,在开始开辟空间的时候会对原空间进行释放,此时如果没有初始化就是野指针,那么此时释放就会出错。
2、这里对于元素的拷贝不能用memcpy,因为memcpy拷贝的时候进行的是浅拷贝,在vector析构的时候,就会出现析构两次的情况。
当然这里也可以不用reserve函数,自己来开辟空间进行拷贝构造。
vector(const vector<T>& v)
//: _start(nullptr)
//, _finish(nullptr)
//, _endOfStorage(nullptr)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); ++i)
_start[i] = v._start[i];
_finish = _start + v.size();
_endOfStorage = _start + v.capacity();
}
赋值运算符重载
赋值运算符的重载多采用现代写法
void swap(vector<T>& v)
{
//用域限定符首先查找全局的
::swap(_start, v._start);
::swap(_finish, v._finish);
::swap(_endOfStorage, v._endOfStorage);
}
//如果是用现代写法的话,在交换之后v就会销毁,如果不初始化为空指针,就会销毁随机空间,这是错误的
//用交换函数这里不能带引用,否则就会改变原来的值
vector<T>& operator=(vector<T> v)//之所以可以交换,是因为在传参的时候进行了深拷贝,交换之后v就会销毁,刚好将原来的空间释放
{
swap(v);//这里的交换函数最好自己实现一下,如果用库函数给的,就需要多次进行深拷贝
return *this;
}
现代写法的赋值运算符重载是通过交换两个不同的vector来完成的。为了不改变原vector的值,在传参的时候没有加引用,此时编译器会拷贝构造一份,我们将该vector与当前的进行交换。交换之后v会自动销毁,完成拷贝构造的目的。
注意:
1、由于现代写法的拷贝构造需要用到拷贝构造,并且交换之后会进行释放,那么这里就需要将拷贝构造进行初始化。
2、这里的交换函数最好自己实现一下,如果用库函数给的,就需要多次进行深拷贝。
reserve函数和resize函数
reserve函数是用来改变vector的空间容量的。reserve函数分为两个方面,如果改变的空间容量小于当前容量,reserve函数是什么都不做的,也不会去改变空间容量;如果改变的空间容量大于当前容量,reserve会将vector的空间改为想要开辟的容量。
vector开空间的时候是需要付出代价的,也就是重新配置,移动数据,释放原空间三步。
//reserve函数
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();//要将原本size的长度保存下来,否则后面扩容之后就会改变指针
//delete[] _start;//销毁_start也就意味着将其他的指针也销毁了
iterator tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T)*sz);
//memcpy拷贝是浅拷贝,也就是直接将源文件的空间给到目标文件的空间
//如果是内置类型或适合浅拷贝类型,这样做是没问题的
//如果涉及到资源管理,那么就会出现释放两次的情况
for (size_t i = 0; i < sz; ++i)
tmp[i] = _start[i];//可以直接赋值,这样对于一些深拷贝的类型,赋值重载是深拷贝,完全没有问题
delete[] _start;//销毁_start也就意味着将其他的指针也销毁了
}
_start = tmp;
_finish = _start + sz;
_endOfStorage = _start + n;
}
}
resize函数是用来改变当前有效空间的大小,并且可以为新的空间赋值。
resize实现时分为三类:
第一类是改变的长度小于当前有效长度,那么此时只需要将_finsh的位置前移即可。
第二类是改变的长度大于有效长度,但是小于空间容量。此时只需要将新增的长度赋值即可。
第三类是改变的长度大于空间容量,那么就需要先开辟新的空间,然后在新增的长度中赋值。
可以发现,第一类就是将有效长度减去即可。第二类和第三类空间的增长与reserve函数相同,那么可以调用reserve函数,只需要对新的长度赋值即可。
//resize函数
void resize(size_t n, T val = T())
{
if (n > size())
{
reserve(n);
int len = n - size();
while (len--)
{
*_finish = val;
_finish++;
}
}
else
{
_finish -= (size() - n);
}
}
插入函数
尾插
vector的尾部插入就是在_finish的位置插入一个元素,这里只需要考虑的是空间满了的扩容情况。
//尾插
void push_back(const T& val)
{
//如果空间够了就扩容
if (_finish == _endOfStorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
insert函数
insert函数是在任意位置进行插入,由于vector是连续的空间,因此在任意位置插入是需要进行数据的挪动的,代价比较大。
单个元素的插入
//1、单个元素的插入
iterator insert(iterator pos, const T& val)
{
assert(pos);
//如果空间不够就扩容
if (_finish == _endOfStorage)
{
int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
iterator ptr = _finish;
//挪动数据
while (ptr != pos)
{
*ptr = *(ptr - 1);
--ptr;
}
*pos = val;
_finish++;
return pos + 1;
}
插入多个元素
//2、插入多个元素
void insert(iterator pos, int n, const T& val)
{
assert(pos);
//扩容之后空间就变了,因此需要记录下原来元素的坐标
size_t old_sz = pos - _start;
iterator new_ptr = _finish + n;//得到插入多个元素之后的长度
//大于容量则需要扩容
if (new_ptr > _endOfStorage)
reserve(new_ptr - _start);
pos = _start + old_sz;
iterator ptr = pos;
//挪动数据
while (ptr != _finish)
{
*(ptr + n) = *ptr;
++ptr;
}
_finish += n;
//从pos位置开始放入多个元素
while (n--)
{
*pos = val;
pos++;
}
}
插入一段区间
//3、在某个位置插入区间
template<class InputIterator>
void insert(iterator pos, InputIterator first, InputIterator last)
{
assert(pos);
//首先算出要插入的长度
int len = last - first;
size_t old_sz = pos - _start;
iterator new_ptr = _finish + len;//得到插入多个元素之后的长度
//大于容量则需要扩容
if (new_ptr > _endOfStorage)
reserve(new_ptr - _start);
pos = _start + old_sz;
iterator ptr = pos;
//挪动数据
while (ptr != _finish)
{
*(ptr + len) = *ptr;
++ptr;
}
_finish += len;
while (first != last)
{
*pos = *first;
pos++;
first++;
}
}
删除函数
尾删
//尾删
void pop_back()
{
//这里要断言一下,如果为空则不能尾删
assert(!empty());
_finish--;
}
erase函数
删除单个元素
//1、删除单个元素
iterator erase(iterator pos)
{
assert(pos);
iterator ptr = pos + 1;
while (ptr != _finish)
{
*(ptr - 1) = *ptr;
ptr++;
}
_finish--;
return pos;
}
删除一段元素
//2、删除一段元素
void erase(iterator first, iterator last)
{
int len = last - first;
iterator ptr = last + 1;
while (ptr != _finish)
{
*(ptr - len) = *ptr;
ptr++;
}
_finish -= len;
}
重载[]运算符
重载[]运算符能够使得vector像数组一样去访问元素
//重载[]运算符
T& operator[](size_t n)
{
return *(_start + n);
}
const T& operator[](size_t n) const
{
return *(_start + n);
}
其他函数
size()函数
//size函数
size_t size() const
{
return _finish - _start;
}
capacity()函数
//capacity函数
size_t capacity() const
{
return _endOfStorage - _start;
}
析构函数
//析构函数
~vector()
{
if (_start)
delete[] _start;//销毁_start相当于将整个vector销毁
_start = _finish = _endOfStorage = nullptr;
}