vector才算是打开了泛型编程的大门,在之前的几篇博客中你可以明显看到vector的构造,等接口的参数多了一个templete,毕竟他可以存任何的数据类型,着篇博客会涉及到一些STL源码(让我们看看大神是如何实现的),且依旧沿袭上篇博客,我只实现我觉得比较难的接口
源码中是用iterator 创建了三个变量,start(开始)finish(结尾) end_of_storage(容量(capacity))
经过上篇博客的洗礼iterator应该比较熟悉了吧,其实就是迭代器,但是vector底层是一个线性的结构也就是数组,那么Iterator一个T*
开空间,深拷贝,现代写法
void Construction(size_t N=1)//开辟空间
{
N=N>capacity()?N:capacity()*2;
Vector<T>tmp(N);
for(int i =0;i<capacity();i++)
{
tmp[i]=start[i];
(tmp.finish)++;
}
swap(tmp, *this);
}
构造列表函数,每个构造函数都要写独立封装一个让代码更简洁
void StructList(const size_t num)
{
start=new T[num+1];
finish=start;
end_of_storage=start+num+1;
}
上面基础结构有用的了迭代器那么我们优先事项这一块
typedef int* iterator;
iterator begin()
{
return start;
}
iterator end()
{
return finish;
}
const iterator begin()const
{
return start;
}
const iterator end()const
{
return finish;
}
iterator rebegin()
{
return finish-1;//最后一个元素的前一个元素
}
iterator reend()
{
return start-1;//第一个元素的上一个元素的
}
默认构造
Vector(size_t num =5)
{
StructList(num);//构造列表函数
}
拷贝构造
vector(const vector<T>& v)//用另一个vector初始化
{
StructList(num);//构造列表函数
_start=new T[v.size()];
_finish=_endOfStorage=_start+v.size();
auto tmp=v._start;
for(int i=0;i<v.size();i++)
{
_start[i]=tmp[i];
}
}
赋值拷贝
void operator =(Vector<T>val)
{
swap(*this,val);
}
迭代器区间构造
也就是和stirng 不同的地方,且后面的容器基本都支持用迭代器区间初始化,泛型编程必备,现阶段看这个实现和拷贝,用一个模版去推你是啥类型的迭代器
template <class InputIterator>
Vector(InputIterator first,InputIterator end)
{
Construction(end-first);
while(first!=end)
{
start=*first++;
}
finish=end-first;
}
push_bakc
不难发现的与string对比起来基础没差,一样没有空间开空间,插入数据…………,但是这里数据类型是根据模版显示实例化的 ,本质就是底层是物理的连续空间
void push_back(const T&val)//T看到了吗,来咯泛型编程的第一个接口
{
if(finish>=end_of_storage)//开辟空间
{
Construction();
}
*finish=val;
finish++;
}
[]
沿袭string的传统,[]需要重载俩个一个为const版的,一个为非const版本的,这一块操作赋重载又又发挥了作用
代码:
T& operator[](const size_t index)
{
return start[index];
}
const T& operator[](const size_t index)const
{
return start[index];
}
insert
这一块涉及了一个知识叫做迭代器失效,请观看这篇 iterator 博客,这里就不深入研究了,这儿有俩构造
- 常数作为下标
- 迭代器作为下标(实现方法一直上面的说了vector的迭代器就是一个指针)
const size_t&insert ( const size_t & pos,const T&val)
{
if(size()+1>=capacity())
{
Construction();
};
for(int i=size();i>pos;i--)
{
start[i]=start[i-1];
}
start[pos]=val;
finish++;
return pos++;//返回下一个元素的下标
}
erase
const size_t&erast (const size_t& pos)
{
finish--;
int ret=pos;
int count=size()-ret-1;//需要挪动的次数
while(count--)
{
start[ret]=start[++ret];
}
return pos+1;//pos下一个位置的下标
}
因为底层是一个数组,所以像这类insert与erase这种接口最好少用,复杂度太高(数据如果有几亿个数据然后头叉)
size
这一块稍微与string有些出入的是,他没有用size和capacity去记录,而是用指针去指向了末尾元算,但是幸运的是,他物理空间是连续的,且指针是可以减指针的(不理解请参考博主这篇博客),那么finish-start那不就得到元素的个数吗
代码:
size_t size()
{
return finish-start;
}
reserve
沿袭string,容器中的reserve基本都是大于capacity就开空间小于啥也不干
void reserve(size_t n)
{
if(n>capacity())//开辟空间
{
Construction(n);
}
}
resize
这个接口需要考虑三个条件
- 大于capacity,增容,并初始化
- 小于size,缩短finish为n的长度,不更改end_of_storage
- 在size<=N<=capacity,,初始化数据到N的位置
代码:
void resize(size_t n,const T&numb)//源码中会构造一个缺省值,这我们就直接不搞了
{
if(n<size())
{
finish=start+n;
}
else if(n>capacity())//开辟空间
{
Construction(n);
}
for(int i=size();i<n;i++)//初始化空间
{
*finish++=numb;
}
}
上面仔细看会发现vector中的参数一般都是模版参数,而string就是char,因为string就是为字符而生的,vector为大众服务,后面的容器和vector一样都是好同志为大众服务