Vector
我们可以把vector简单的理解为顺序表,它的数据是连续的,也可以通过下标去访问,让我们先来了解它的结构。
private:
iterator _start;//记录开头位置
iterator _finish;//记录有效数据的结束位置
iterator _endofstorage;//记录总共开辟多少空间
而它的实现是以模版的方式实现的,为了可以满足各种类型的使用。也因此,它的迭代器类型也有所改变。
template<class T>//模版类型
class vector
{
public:
//迭代器的类型也随着模版而改变
typedef T* iterator;
typedef const T* const_iterator;
//同样,既然是顺序表,那么它的打印或者遍历就是以区间的方式打印
void printv(const vector<T>&v)
{
typename vector<T> ::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << "";
++it;
}
cout << endl;
for (auto e : v)
{
cout << e << "";
}
cout << endl;
}
//迭代器取到头尾,以及const版本
iterator begin()
{
return _start;
}
const_iterator begin()const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
而它的构造和析构也很简单,因为是连续的,只要确认头尾即可。
vector()//直接给三个空指针即可
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
}
~vector()//清空间再置空即可
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
而它的拷贝构造就有点小坑。
//首先需要考虑空间满了扩容的问题
void reserve(size_t n)
{
if (n > getcap())
{
size_t sz = getsize();
//因为memcpy会把地址一起拷贝过来 所以这里反而不能使用memcpy 只能开空间然后传数据
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
//传完数据再改变一下指向
_start = tmp;
_finish = _start + sz;
_endofstorage = _strat + n;
}
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
//首先先看看要不要扩容
reserve(v.getcap());
//然后再直接尾插即可
for (auto& e : v)
{
push_back(e);
}
}
//交换
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector<T>& operator=(vector<T> v)
{
//因为传参会调用拷贝构造,那么我们直接交换,这样可以省事
//又因为this指针,所以直接给v即可,最后返回*this
swap(v);
return *this;
}
它的基本结构就是这些,那么接下来我们来看看它的基本功能
//尾插
void push_back(const T& x)
{
assert(pos >= _start);
assert(pos<= _finish);
//还是先确认要不要扩容
if (_finish == _endofstorage)
{
reserve(getcap() == 0 ? 4 : getcap() * 2);
}
//然后直接插入数据然后下标++即可
*_finish = x;
++_finish;
}
//尾删就更简单了 如果它不为空的话直接下标--
void pop_back(const T& x)
{
assert(!empty());
--_finishi;
}
//任意位置插入
void insert(iterator pos,const T& x)
{
//还是老样子先看看要不要扩容
if (_finish == _endofstorage)
{
//为了规避迭代器失效 所以我们的pos得用相对位置
size_t len = pos - _start;
reserve(getcap() == 0 ? 4 : getcap() * 2);
pos = _start + len;
}
//然后把pos开始 到finis结束位置的数据都往后移一下 然后插入即可
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finishi;
//但是你无法保证每次都扩容,所以在插入后迭代器会失效 不要使用它
}
//删除
iterator erase(iterator pos)
{
asssert(pos < _finish);
assert(pos >= _start);
//先记录我pos位置的下一个
iterator it = pos + 1;
//然后从后往前覆盖即可完成删除
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
//最后--有效位置下标即可
--finish;
//最后返回pos 此时因为删完了 所以pos是删之前的下一个 把它更新给迭代器就可以避免迭代
器失效的问题了
return pos;
}
迭代器失效
如果咱认真想想删除的逻辑,我们会发现,如果我们删除或移动完某一个数据之后,之前的下标就不能代表它了,因为数据都有所改变,这个就叫迭代器失效。那么解决它的方法无非就是停止使用改变后的迭代器,用新的,比如erase,它就会返回新的pos,这样规避迭代器失效的问题。
剩下的就是一些收尾了,主要的功能都已经实现了
T& operator[](size_t pos)
{
//直接返回下标位置的数据即可
assert(pos < getsize());
return _start[pos];
}
const T& operator[](size_t pos)const
{
//再给个const版本即可
assert(pos < getsize());
return _start[pos];
}
//因为咱的头尾都是指针 所以想要确认有几个数据只能用指针-指针 这个实现起来也是简简单单的
size_t getcap()
{
return _endofstorage - _start;
}
size_t getcap()const
{
return _endofstorage - _start;
}
size_t getsize()
{
return _finish - _start;
}
size_t getsize()const
{
return _finish - _start;
}