大家好久不见,今天我们一起来模拟实现一下STL中的vector容器。
因为物理空间连续,所以我们选择直接将原生指针作为迭代器,且整个vector容器的容量(capacity)和大小(size)均是采用三个迭代器相减来表示,有关迭代器前开后闭,我在string的模拟实现中已经讲解,不理解的可以回去看看。
namespace teacher
{
template<class T>
class vector
{
public:
//原生指针作为迭代器
typedef T* iterator;
typedef const T* const_iterator;
//容量和大小
int capacity() const
{
return _end_of_storage - _start;
}
int size() const
{
return _finish - _start;
}
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}
不管是构造函数还是拷贝构造,都是要把元素一个一个的赋值上去,因此先写一个push_back函数,在其他构造中调用即可。
另外c++允许在一个模板里的函数使用模板。
需要说明的是,匿名对象的生命周期只在那一行,但如果是const T& x 修饰,生命周期就会延长到x结束为止。
vector() {};
vector(size_t n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(),v.end());
swap(tmp);
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
当我们的vector内存有指向其他位置的指针的时候,简单的字节拷贝(值拷贝
)会让两个vector指向同一个位置。这个问题非常严重,因为如果指向同一位置,意味着要析构多次,这会让我们的程序崩溃,因此我们首先来解决一下深浅拷贝的问题。
如下图所示的扩容逻辑,可以看到我们为vector的每一个元素都重新开辟了空间,这样可以避免浅拷贝的问题。
void reserve(size_t n)
{
size_t sz = size();
// 容量不够才进行扩容
if (n > capacity())
{
T* tmp = new T[n];
//拷贝原来的内容
if (_start)
{
for (int i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
但这样写也会有一个很严重的问题,那就是 tmp[i] = _start[i]
这一步其实也是一个浅拷贝,即会出现如图所示的问题:
为了解决这个问题,我们需要手写一个深拷贝的 赋值重载
。
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
vector<T> operator=(vector<T> v)
{
//v是临时对象,传过来完成了拷贝构造,走了push back的逻辑,因此再交换是开了新空间的。
swap(v);
return *this;
}
因为我们习惯了数组的[]读取数据,并且vector的内存连续,所以我们重载一下[]
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
逻辑与string的模拟实现基本一样,因为这里是迭代器也不用考虑npos的问题。
尽管这里都返回了迭代器,但使用这两个函数后,我都建议你不要再使用pos,认为pos已经失效!
iterator insert(iterator pos, const T& val)
{
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 20);
pos = _start + len;
//扩容后更新pos 防止pos失效
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
iterator erase()
{
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}
这三个函数较为简单,一笔带过
bool empty()
{
return _start == _finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
void resize(size_t n,T val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
以上就是本篇文章全部内容了,希望对你有所收获,我们下次再见