上一篇我们对vector一些常用的函数进行了讲解,本篇博客我们就对vector进行模拟实现,以便于我们更好地了解vector的使用以及对一些常见bug的认识
有了string类的模拟实现,vector的模拟实现我们上手起来就简单一点了:
首先为了和库里面的vector混淆视听,放入自己命名的空间里,并且根据vector的源码分析我们得出了三个成员变量:
分别是:
其实他们实质上都是指针,位置大概是这样的,遵循左闭右开的规则
_start
_finish
_endofstorage
这样一个简单的框架就构造出来了:
template是模板初阶我们学习过的,里面的T可以是char,int等任意类型
namespace jh
{
template
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
};
capacity就是endofstorage到start之间的可存储元素个数
size就是finish到start之间的有效元素个数
size_t capacity() const
{
return _endofstorage - _start;
}
size_t size() const
{
return _finish - _start;
}
尾插函数在很多地方可以复用,所以我们首先解决了尾插,为后面的函数进行模拟实现提供了基础:
插入首先就是要判断是否已满,所以我们先检查是否需要扩容,然后将当前finish位置的值置为x,finish的位置要往后移动一位
void push_back(const T& x)
{
if (_finish==_endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*(_finish) = x;
_finish++;
}
同样的insert插入函数也需要复用,我们来解决它:
我们首先做的就是断言,pos一定是要在start和finish的区间之内的
当finish和endofstorage相等时就需要扩容,但是这里考虑到迭代器失效的问题我们就要记录pos和start原本之间的元素个数,然后再给start赋值
最后用while循环移动元素,移动完成后将pos位置的值置为x,同时finish位置往后移动一位即可
void insert(iterator pos, const T& x)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
_start = pos + len;//考虑到扩容之后迭代器失效的问题,所以记录位置
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
end--;
}
*pos = x;
_finish++;
}
迭代器是很常用的一个知识点,相信我们之前的学习中早已经熟悉了他的用法,很简单,但是我们需要对权限的放大有一个照顾,所以加上const
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
构造函数主要用于初始化,他的作用很大也很常见,但是这里vector的构造函数可以用一个特殊的迭代器初始化
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
还可以以这种方式,T()默认就是0,和上面一样,直接用pushback尾插进去,当然这里的T()其实是C++的一个匿名函数,通常我们所说匿名对象的生命周期只有一行,但是用const修饰后的匿名对象的生命周期会延长!
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
拷贝构造我们直接用for循环解决,然后调用pushback ,很简单的
vector(const vector& v)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
析构函数直接重拳出击了属于是(狗头)
用delete清空_start的空间,然后将其全部置为空
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
swap函数其实我们的用处不大,我们直接复用std标准库里的swap:
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
也很简单,我们偷点懒用直接复用swap函数:
交换后返回*this即可
vector& operator=(vector tmp)
{
swap(tmp);
return *this;
}
直接返回start下标指定下标的值即可,当然前提是这里的pos是要小于size的
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
其实我们可以将reserve先实现后直接将reserve复用再resize上,直接扩容
reserve扩容时只有n是大于原本的capacity时才会扩容,向前几篇博客所说的,不会进行缩容,然后我们记录有效元素个数sz=size(提前记录好是因为后面会进行delete释放原本start空间的操作),如果start不为空,就将start中的元素按照深拷贝的方式用for循环放入提前开辟好的tmp空间里,然后将tmp给予start,finish依旧时start+size,但是endofstorage变成了start+n
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
resize函数的扩容我们就用reserve来实现,但是resize可以会初始化:
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start+n)
{
*finish = val;
finish++;
}
}
}
这里有一个需要注意的点:
erase会返回被删除元素的下一个元素的迭代器!
断言我相信大家都是信手拈来了
我们将pos位置后的元素往前移动一位即可!
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
_finish--;
return pos;
}
好了,今天的分享到这里就结束了,感谢大家的支持!