目录
前言
前面我们学习了如何使用vector的各种接口,为了深层次的理解掌握vector,本文模拟实现一个vector,实现vector主要的核心接口,并可以用我们实现的vector来做题(我们的标准);
一、大体框架
我们的实现模仿SGI版本的vector,因此,命名上与实现方式也类似;一下为vector的大体框架;
namespace MySpace
{
template<class T>
class vector
{
// 定义vector迭代器
typedef T* iterator;
typedef const T* const_iterator;
public:
private:
// 指向第一个元素位置的指针
iterator _start = nullptr;
// 指向最后一个元素位置的指针
iterator _finish = nullptr;
// 指向最大容量的下一个位置的指针
iterator _end_of_storage = nullptr;
};
我们通过三个指针来管理这段连续的内存空间,完成这个容器模拟增删查改的过程;
二、构造函数与析构函数
构造函数实现了有如下几个;
// 构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
vector(size_t n, const T& val = T())
{
reserve(n);
for(size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
}
// 提供重载版本防止调用到下面迭代器构造
vector(int n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
// 拷贝构造
vector(const vector& v)
{
T* tmp = new T[v.capacity()];
// 不能使用memcpy,因为这里需要深拷贝
for (size_t i = 0; i < v.size(); i++)
{
// 这里使用了[]重载(后面会实现)
// 如果vector中存的是内置类型,则=是赋值,如果vector中存的是自定义类型,则会调用自定义类型的赋值重载
tmp[i] = v[i];
}
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
赋值重载(传统写法)
//vector& operator=(const vector& v)
//{
// T* tmp = new T[v.capacity()];
// // 注意深拷贝
// for (size_t i = 0; i < v.size(); i++)
// {
// tmp[i] = v._start[i];
// }
// delete[] _start;
// _start = tmp;
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
// return *this;
//}
// 赋值重载(现代写法)
vector& operator=(vector v)
{
// 这里的swap也是后面实现的swap,不用算法库中的swap是为了提高效率
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
其中第三个构造函数重载的意义是重载参数为int类型的n,因为当构造函数参数都为两个int时,它会匹配到迭代器那个构造函数中去;
注意:所有需要拷贝的地方都需要想一想是否需要用深拷贝,如果用深拷贝,则必须要考虑怎么深拷贝,不能用mencpy进行拷贝,memcpy拷贝是浅拷贝;
三、迭代器
迭代器有以下接口,本文暂时实现正向迭代器与const修饰的迭代器;
// 迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
// 返回const迭代器
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
四、容量相关接口
有如下几个容量相关接口,这里也挑一些常用的实现;
// capacity
// 指针相减,得到就是中间元素个数
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
bool empty()
{
return _start == _finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
// 注意:这里不能用memcpy!!因为memcpy是浅拷贝!
// 如果vector中存的不是内置类型,可能会出现不可预知的错误
for (size_t i = 0; i < size(); i++)
{
// 如果是内置类型则赋值,如果是自定义类型,则调用其赋值重载
tmp[i] = _start[i];
}
size_t len = size();
// 释放原来就空间
delete[] _start;
_start = tmp;
// _finish = _start + size(); err
// 上式等同与 _finish = _start + _finish - _start;
// 而且由于此时start已经发生改变,计算的结果未知
_finish = _start + len;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
// 查看是否需要扩容
if (n > capacity())
{
reserve(n);
}
for (size_t i = size(); i < n; i++)
{
// 对于自定义类型,调用赋值重载进行深拷贝
_start[i] = val;
}
_finish = _start + n;
}
}
resize与reserve这两个函数都是需要考虑在不同的参数下,做出的反应是什么,要分别考虑参数n小于size,大于size而小于capacity,大于capacity这三种情况;我们的原则与库里的原则一致,能不缩容,坚决不所容,因为缩容所消耗的代价太大了;
五、访问相关接口
在访问vector时,我们常常会提供 [ ] 重载,使其访问行为与数组一致,同时我们还会提供一个at接口,at与[]的功能一致,当提供参数非法时,at通常是抛异常,而 [ ] 则是粗暴的断言方式;
// access
T& operator[](size_t i)
{
assert(i >= 0);
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i >= 0);
assert(i < size());
return _start[i];
}
T& front()
{
return _start[0];
}
const T& front() const
{
return _start[0];
}
T& back()
{
return _start[size() - 1];
}
const T& back() const
{
return _start[size() - 1];
}
同时,每个访问接口我们都提供了两个版本,一个版本是用于const对象,一个则是普通对象调用的函数;
六、修改对象相关接口
最后一批接口为修改对象的一批接口,通常是对对象进行增删的接口,查找接口通常用算法库中的find函数即可;
// modify
void push_back(const T& val)
{
// 查看是否需要扩容
if (_finish == _end_of_storage)
{
reserve(capacity() + 1);
}
*_finish = val;
++_finish;
}
void pop_back()
{
assert(size() > 0);
--_finish;
}
void insert(iterator pos, const T& val)
{
// 检查是否需要扩容
if (size() + 1 == capacity())
{
// 如果扩容了要更新pos位置(迭代器失效问题)
size_t len = pos - _start;
reserve(capacity() * 2);
pos = _start + len;
}
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
end--;
}
*pos = val;
_finish++;
}
void insert(iterator pos, size_t n, const T& val)
{
// 检查是否需要扩容
if (size() + n > capacity())
{
// 扩容后要更新pos位置(迭代器失效问题)
size_t len = pos - _start;
reserve(size() + n);
pos = _start + len;
}
iterator end = _finish + n - 1;
while (end >= pos + n)
{
*end = *(end - n);
end--;
}
end = pos;
for (size_t i = 0; i < n; i++)
{
*end = val;
end++;
}
_finish += n;
}
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos;
while (end < _finish - 1)
{
*end = *(end + 1);
end++;
}
--_finish;
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
void clear()
{
_finish = _start;
}
其中,尤其需要注意的是在insert中出现的迭代器失效的问题;如果我们扩容了,扩容前,我们要保存其相对位置,因为扩容后,pos指向的是之前需要扩容的位置;
七、源码
由于代码庞大,本文所有源码都保存在了gitee上,可通过以下链接来访问;