从本质上看Vector就是数组,一种顺序存储的容器,c++将它作为一种类封装更便于使用
在c++标准库里面可以看到它的实现,和我们经常用的数组不同,库里面并不是使用我们熟知的指向首元素的指针+元素个数+容量大小,而是使用了三个指针,分别指向的是,首元素,最后一个元素末尾,容量结束末尾。
为了能够使用更多种类型,可以写一个模板,而这三个指针也应当用传统的迭代器,迭代器的类型则是模板实例化后的指针类型。
在模拟实现时为避免和库里的Vector混淆,可以在外面加一层命名空间。
namespace zz
{
template<class type>
class Vector
{
public:
typedef type* iterator;
typedef const type* const_iterator;
//...
private:
iterator _begin;
iterator _end;
iterator _end_storage;
};
}
涉及到开辟和销毁空间,理应交给构造函数和析构函数来做。
Vector()
{
_begin = nullptr;
_end = nullptr;
_end_storage = nullptr;
}
~Vector()
{
if (_begin)
{
delete[] _begin;
_begin = nullptr;
_end = nullptr;
_end_storage = nullptr;
}
}
有时候想获取它的三个指针应该需要通过函数返回,不希望被修改则使用const保护。
还有的时候想要获取他的容量和数据个数也可以用函数返回
而想要初始化可以借助拷贝构造或者赋值来完成。
const_iterator end_storage()const
{
return _end_storage;
}
const_iterator end()const
{
return _end;
}
const_iterator begin()const
{
return _begin;
}
size_t capacity()const
{
return _end_storage - _begin;
}
size_t size()const
{
return _end - _begin;
}
Vector(const Vector<type>& v)
{
_begin = new type[v.capacity()];
//memcpy(_begin, v._begin, v.size());
iterator it1 = _begin;
iterator it2 = v._begin;
while (it2 != v._end)
{
*it1 = *it2;
it1++;
it2++;
}
_end = _begin + v.size();
_end_storage = _begin + v.capacity();
}
Vector<type>& operator=(Vector<type> v)
{
swap(v);
return *this;
}
这样的=操作符看起来较为简洁,但理解起来是,先传进来一个和实参一样的临时变量,然后swap交换this里面的内容和这个临时变量,最后返回Vector<type>类型的*this。并且这里也不用担心会有内存泄漏的问题,因为这里的v只是一个临时变量,出来函数以后会自动销毁。
函数内部用到的swap也非常简单,只需要将两个对象里面的私有成员交换即可。
void Swap(Vector<type>& v)
{
std::swap(v._begin, _begin);
std::swap(v._end, _end);
std::swap(v._end_storage, _end_storage);
}
vector和数组存储数据的方式一样,也是顺序存储,所以【】下标随机访问很容易实现
只是这里可以用一种只读和一种可读可写的方式重载此操作符。
Vector<type>& operator[](size_t pos)
{
assert(pos < size());
return _begin[pos];
}
const Vector<type>& operator[](size_t pos)const
{
assert(pos < size());
return _begin[pos];
}
插入数据首先要考虑扩容,库里面用的是reserve这个函数,参数使用的是新空间的大小。
reserve开辟空间时,开辟空间成功后,并不能简单的直接修改指针指向的位置,即使让_begin指向了新空间,那么_size和_capacity也能如我所愿指向新空间该指向的位置吗?不能这样做的原因是这样会导致迭代器失效,_begin指向新空间后,新的_size,_capacity需要通过_size(),_capacity()返回的偏移量来确定位置,而此时_begin已经指向了新空间,_size,_capacity还指向旧空间,试图用两个毫不相关的指针相减显然是荒谬的。所以一定先要把偏移量保存起来,_capacity的偏移量却无需保存,因为它的偏移量已经被形参表示了。
void Pushback(int x)
{
if (capacity() == size())
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_end = x;
_end++;
}
void reserve(size_t n)
{
if (n > size())
{
type* tmp = new type[n];
if (tmp)
{
size_t sz = size();
memcpy(tmp, _begin, sz*sizeof(type));
delete[] _begin;
_begin = tmp;
_end = _begin + sz;
_end_storage = _begin + n;
}
}
}
插入数据和删除数据都需要挪动数据,插入数据仍需考虑扩容。
void Insert(size_t pos, type val)
{
assert(pos <= size());
if (capacity() == size())
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
for (size_t i = size()-1; i >= pos; i--)
{
_begin[i+1] = _begin[i];
}
_begin[pos] = val;
_end++;
}
type Erase(size_t pos)
{
assert(pos <= size());
type del_val = _begin[pos];
for (size_t i = pos+1; i < size(); i++)
{
_begin[i-1] = _begin[i];
}
_end--;
return del_val;
}
打印数据时有些地方必须用到库里面的东西,得要指明,在前面加上std::可以用迭代器也可以用范围for,但一般的容器都使用主流的迭代器。迭代器的力量不容小觑,在list等容器中也是主流。
void Print()
{
iterator it=_begin;
while (it != _end)
{
std::cout << *it<<' ';
it++;
}
std::cout << std::endl;
}