vector的大部分逻辑和string实现类似,我们先来看都要实现vector的哪些功能:
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator cbegin()const
{
return _start;
}
const_iterator cend()const
{
return _finish;
}
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _end_of_storage - _start;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
事实上,vector私有成员只有三个迭代器,我们可以通过三个迭代器的相对位置来计算出目前vector的size,capacity等,并且可以获取到begin和end位置的迭代器。
vector的构造函数:
//什么都不传,默认全为空
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
vector(size_t n, const T& value = T())
{
reserve(n);
for (size_t i = 0; i < n; ++i)
push_back(value);
}
vector(int n, const T& value = T())
{
reserve(n);
for (int i = 0; i < n; ++i)
push_back(value);
}
//拷贝构造
vector(const vector<T>& val)
{
reserve(val.capacity);
for (auto e : val)
push_back(e);
}
//传迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
第一个什么都不传,在初始化链表将成员全部初始化为nullptr。
第二个和第三个都是初始化n个T类型,但是为什么要有size_t 和 int 的区分呢?这是因为要避免和传迭代器区间构造的冲突,我们来看下面的构造:
int main()
{
vector<int> v(10,1);
return 0;
}
编译器默认会把10,1认为是整形,此时在我们看来应是初始化了10个1,在编译器看来,如果我们只写了size_t和迭代器区间构造,那么上面这种做法编译器会优先考虑迭代器,因为这是最匹配的一种,因此到时候就会对整形进行解引用操作,肯定会报错,因此我们又补加了如下:
vector(int n, const T& value = T())
{
reserve(n);
for (int i = 0; i < n; ++i)
push_back(value);
}
这样就不会冲突了。最后我们来看构造n个T的原理:
原理也非常简单,我们直接给其reserve n个空间,空间肯定足够,然后循环n次,不断将value尾插即可。
这里我们使用了reserve 和 push_back函数,我们先来看看这两个:
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < oldsize; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
reserve(capacity() == 0 ? 4 : 2 * capacity());
*_finish = x;
++_finish;
}
这里的reserve 和我们实现的 string中的略有不一样,其他的地方基本一致,我们来看看不同点:
- 我们不直接使用memcpy了,为什么呢?
- 这里我们首先求出了原来的size大小,为什么呢?
首先我们来解释第一点:
(如果是memcpy) 这里结合reserve 代码来看,好像没错啊,把上面的memcpy拷贝下来,再释放原来的空间,而且原来的_start也delete掉了。
但是我们要注意,如果是内置类型例如整形,这样写是不会出错的,但是我们别忘了,我们实现的vector里面也可以存其他的自定义类型例如 string,如果是自定义类型(拿string来举例),我们使用memcpy拷贝的是每个字节,每个string里面还有单独的一个_str指针,memcpy只是简单的浅拷贝,因此我们虽然delete了原来的_start,但是最后析构的时候就会报错。
再来看第二点:
为什么要求出原来的size大小呢(也就是为什么不能写成_finish = _start + size() ):
还是扩容的原因,此处就比较简单了,因为我们扩容了,_start已经发生改变,因此我们需要先求出oldsize,此后计算就可以方便许多。
push_back很简单,扩容后插入即可,这里就单独解释。
我们再来看拷贝构造和传迭代器区间构造:
//拷贝构造
vector(const vector<T>& val)
{
reserve(val.capacity);
for (auto e : val)
push_back(e);
}
//传迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
拷贝构造也比较简单,我们提前开好和val一样大的空间,然后遍历val不断将其内容尾插入即可。
迭代器区间构造,这里就考我们对迭代器理解的深度了,其实本质也是很简单,就我们目前实现的简单vector,暂且先把其理解为传入了两个指针,然后对这两个指针区间内容操作即可,把first的解引用不断插入即可。
接下来我们来看几个简单的功能:
bool empty()
{
return _start == _finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
void clear()
{
_finish = _start;
}
T& operator[](size_t x)
{
return _start[x];
}
const T& operator[](size_t x)const
{
return _start[x];
}
这五个功能都比较简单,但是这里着重说一下后两个,对于[ ]运算符重载来讲,我们需要返回该位置的引用。
最后来看几个修改的功能:
void resize(size_t n, const T& val = T())
{
assert(n >= 0);
if (n > capacity())
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
iterator cur = _finish;
while (cur != pos)
{
*cur = *(cur - 1);
cur--;
}
*pos = x;
_finish++;
return pos+1;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator cur = pos;
while (cur < _finish - 1)
{
*cur = *(cur + 1);
cur++;
}
--_finish;
return pos;
}
先来看resize功能,我们在string里面也学习过resize函数,如果我们要求改变的size值大于目前的capacity,那么我们首先要进行扩容,然后再不断将默认值写入并且++_finish即可。如果n小于capacity,我们只需要改变_finish位置到 _start + n。
最后的insert和erase函数功能和string里面的功能类似,这里就不仔细讲解全部,但是在insert里面我们要注意的是:因为只要是插入数据,就会牵扯到扩容,如果进行了扩容,那么我们传入的pos位置就会失效,也就是我们所说的迭代器失效问题:
因此我们需要注意的就是,如果进行了扩容,我们需要及时更新新空间中pos 和 _start的相对位置,先提前计算好距离,最终进行 pos = _start + len 即可。
以上内容为自己对于vector学习后的理解,并且简单构造了基础的功能,如果有错误,欢迎批评指正!!!