#pragma once
#include
//vector的模拟实现
#include
#include
#include
#include
using namespace std;
namespace tao
{
template <class T>//定义一个模板T
class vector
{
public:
typedef T* iterator;//将T* 重命名为iterator
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
//成员函数
vector()//空值构造
: _start(nullptr)
, _finish(nullptr)
, _endstroage(nullptr)
{}
vector(const vector<T>& v)//深拷贝
: _start(nullptr)
, _finish(nullptr)
, _endstroage(nullptr)
{
_start = new T[v.size()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start+v.size();
_endstroage = _start+v.capacity();
}
void swap(vector<T> v)
{
std::swap(_start , v._start);
std::swap(_finish ,v._finish);
std::swap(_endstroage ,v._endstroage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endstroage = nullptr;
}
}
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* temp = new T[n];//首先开空间
if (_start != nullptr)
{
//将数据拷贝到temp去
memcpy(temp, _start, sizeof(T) * sz);
//删除原来空间
delete[] _start;
}
//最后将空间赋值给_start
_start = temp;
_finish = _start + sz;
//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
_endstroage = _start + n;
}
}
void push_back(const T& val)
{
//首先要考虑是否扩容
if (_finish == _endstroage)
{
size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
size_t capacity() const
{
return _endstroage - _start;
}
size_t size() const
{
return _finish - _start;
}
T& operator[](size_t pos)
{
assert(pos <= size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos <= size());
return _start[pos];
}
iterator insert(iterator pos,const T &val)
{
assert(pos >= _start && pos <= _finish);
//首先考虑扩容----这里有一个问题:迭代器失效
//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
//需要将将pos迭代器恢复,需要更新pos的新位置。
if (_finish == _endstroage)
{
size_t len = pos - _start;
size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(newcapacity);
pos = _start + len;
}
//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
return pos;
//指向新插入位置的迭代器
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos <= _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
--_finish;
return pos;
//返回的是删除元素的下一个位置的迭代器
}
void pop_back()
{
erase(--end());
}
void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
//填值
while (_finish != n + _start)
{
*_finish = val;
++_finish;
}
}
}
private:
//成员变量
iterator _start;//指向开头位置的迭代器
iterator _finish;//指向真实数据的最后位置
iterator _endstroage;//指向容量的的最后位置
};
}
这里vector的实现需要用到模板,因为vector就是用模板实例化出各种类型的vector。
根据源码,它是按照上图的方式来进行处理的,成员变量是三个迭代器,start迭代器指向开头位置,finish迭代器指向最后一位有效数据的后面,endstroage迭代器指向的是有效容量的后面位置。
而这里的
size=_finish-start;
capcaity=endstorage-start;
迭代器是一种新的类型,需要自己定义,我们这里用typedef在类里定义迭代器的
typedef T* iterator;将T* 重命名为iterator
1.vector最常用的构造就是无参构造了。这里只需要将vector的成员变量都初始化成空就可以了。
vector()//空值构造
: _start(nullptr)
, _finish(nullptr)
, _endstroage(nullptr)
{}
拷贝构造对于自定义类型需要使用深度拷贝,不能浅拷贝。
这里我们跟string类的拷贝构造类似,首先要初始化,给对象开空间,然后将值拷贝过去。对应的成员变量要一致。
vector(const vector<T>& v)//深拷贝
: _start(nullptr)
, _finish(nullptr)
, _endstroage(nullptr)
{
_start = new T[v.size()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start+v.size();
_endstroage = _start+v.capacity();
}
①赋值运算符重载比如v1=v;将v赋值给v1,这里的v1和v都是存在的对象。可以用类似于string类里的赋值重载。首先用tmp开空间,将值拷贝到tmp中去,释放原来的空间,最后再将temp赋值给对象。
②不过我们可以用一个更简单的方式,我们知道对象v传过来函数用形参接收,我们想要就是这个形参的空间大小和数据,然后将原有的v1空间释放,将形参的对象的空间赋给v。我们这里可以直接使用swap函数让v1和v直接交换。这样v1就获得v的空间大小和数据了。最后交换完后的v1就变成形参了,函数结束后v1就会被销毁,即原来的空间被释放。
void swap(vector<T> v)//将v和原对象数据和空间交换
{
std::swap(_start , v._start);
std::swap(_finish ,v._finish);
std::swap(_endstroage ,v._endstroage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
//交换完后,原对象的空间会因为变成形参后函数结束自动销毁。
return *this;
}
判断一下statr是否为空指针,如果为空指针那就不用释放了,如果不为空指针说明还有数据。需要释放,new[] 与delete[]配合使用,释放完后,将指针都置空。防止变成野指针。
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endstroage = nullptr;
}
}
使用下标和方括号来访问变量和遍历。首先我们需要知道对象的大小。而获取大小即finish-start即可。这里我们封装成函数size().
获取pos位置上的数据,而数据是在指针指向的地方,也就是_start[pos]。
T& operator[](size_t pos)
{
assert(pos <= size());//断言判断一下pos位置是否合法
return _start[pos];
}
const T& operator[](size_t pos) const//用于const修饰的对象访问和遍历。
{
assert(pos <= size());
return _start[pos];
}
利用迭代器访问和遍历对象也是很常见的,迭代器我们知道是用typedef T* iterator定义的,将T* 重命名为iterator,其本质上可以看成不同类型的指针。
begin()就是返回指向开头位置的迭代器
end()就是返回指向最后一个数据的后面的迭代器。
迭代器有很多类型,还有const修饰的迭代器,即const T类型的 利用typedef const T const_iterator.将const T*重命名为const_iterator.
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
①尾插一个数据,首先需要判断是否要扩容,可以直接使用reserve()函数扩容,请上改操作里查看reserve()的实现。这里直接使用。这里需要讨论一下,容量是否为0,如果为0那么直接给它开辟4个空间,如果不是0那么就按照两倍的扩容。
②如果不需要扩容直接在finish位置插入数据,记得插入完,finish需要往后挪动一下。
void push_back(const T& val)
{
//首先要考虑是否扩容
if (_finish == _endstroage)
{
size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
①相比较string类的insert这里vector就不太一样了,因为string中的insert使用的还是下标,而这里使用的是迭代器。在pos位置插入val。
①首先需要判断pos位置的合法性
②再判断是否需要扩容
③将数据挪动,从最后一个数据开始挪动,给pos位置留出位置。
④将数据插入pos位置,finish需要往后挪动一下。
⑤最后将pos返回也就是返回新插入数据的位置。
要考虑到如果扩容了,那肯定是异地扩容,那原来pos指向的原空间会因为扩容后,原空间释放,pos就变成野指针了。即pos迭代器失效了,不能再访问pos位置上的数据了,这里的解决方法是要扩容后,要更新pos位置。
使用迭代器的好处就是不用考虑头插时下标要小于0的问题了(string类里遇到的问题).因为迭代器是一个地址,不可以为0的。
iterator insert(iterator pos,const T &val)
{
assert(pos >= _start && pos <= _finish);
//首先考虑扩容----这里有一个问题:迭代器失效
//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
//需要将将pos迭代器恢复,需要更新pos的新位置。
if (_finish == _endstroage)
{
size_t len = pos - _start;//记录pos位置在哪
size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(newcapacity);
pos = _start + len;//扩容完更新pos位置,防止变成野指针。
}
//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
return pos;
//指向新插入位置的迭代器
}
①相比较string类里的erase这里vector的erase也不一样了,用的是迭代器作为位置,而不是下标。
②首先判断pos位置是否合法
③挪动数据,将pos位置覆盖。从前往后挪动。
④挪动完后,将finish往前挪动。
⑤返回pos,这里返回的是被删除数据的下一个元素的位置。
iterator erase(iterator pos)
{
assert(pos >= _start && pos <= _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
--_finish;
return pos;
//返回的是删除元素的下一个位置的迭代器
}
尾删,可以直接复用erase,删除位置也就是end()前面的位置。因为end()指向的是最后一个数据的后面位置。
void pop_back()
{
erase(--end());
}
vector类里没有直接查找的函数比如find,不过在算法里有find,使用迭代器就可以用。
①size()数据的大小,其实就是finish-start。
size_t size() const
{
return _finish - _start;
}
capacity()容量的大小,其实就是endstorage-statr。
size_t capacity() const
{
return _endstroage - _start;
}
①扩容逻辑其实很简单,首先要判断要开辟的空间是否比原空间要大。如果小的话就不用开了。
②首先利用temp保存n大小空间
③然后将原对象数据拷贝到tmp去(如果原对象是空的,那就不用拷贝了直接将空间赋给对象即可)
④拷贝完将原空间释放。
⑤最后将tmp空间和数据赋给_start。_finish位置要更新。
这里要注意finish位置如何更新呢?很多人会这样写:_finish=_start+size();注意这样是不可以的。为什么呢?
因为在更新finish之前,statrt的位置已经改变了,不再指向原来的位置,而finish还是指向原来的空间位置。而size()=finish-start.最终计算出来finish就等于空了。问题就出在start位置已经改变了。所以size()里计算的就不是数据的大小了。我们应该先保存一份start没有改变的数据,一开始就记录一个数据大小,最后再加上就可以了。
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* temp = new T[n];//首先开空间
if (_start != nullptr)
{
//将数据拷贝到temp去
memcpy(temp, _start, sizeof(T) * sz);
//删除原来空间
delete[] _start;
}
//最后将空间赋值给_start
_start = temp;
_finish = _start + sz;
//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
_endstroage = _start + n;
}
}
①resize()是vector最常用的几个接口,可以给对象纪创建空间又可以初始化,一般来说初始化的值是缺省值,不给定的时就使用缺省值初始化,给定值时就用这个值初始化。通常默认是用0初始化,但是这里不可以用0初始化,因为不一定为vector
类型,还可能是其他类型。所以这里给的是T()。
②T() 本质是一个匿名对象,会自动调用默认构造。对于自定义类型,就会调用默认构造。但对于内置类型呢?好像内置类型没有构造函数吧? 因为有了模板,内置类型升级了,也有了类似构造函数,就比如int i=int()。这里默认int()是0,而int j=int(1),这里给j初始化的就是1了。
③resize()可以分成三种情况,第一种ncapacity,这种情况就需要扩容了,然后将多余的初始化。
④不过这里可以将二三情况合并,不管需不需扩容,都使用reserve扩容到n,因为reserve会自动检查是否需要扩容。
⑤最后就需要填值了,从finish开始填值到n+_start位置。
void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
//填值
while (_finish != n + _start)
{
*_finish = val;
++_finish;
}
}
}