类的成员变量
namespace lty{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
}
公有成员变量:
重命名模版类型T*指针为迭代器iterator和const类型的const_iterator迭代器
私有成员变量:
_start为vector的首地址;
_finish为尾地址,注意,_finish中存放的数据应被屏蔽,无法使用,可以理解为字符串中的 ’\0’ 字符,遇到则结束;
_end_of_storage为内存最大地址,即这片空间末尾的地址,一般扩容时会修改这个成员的大小。
Tip:C++11后支持私有成员缺省赋值,可以不必在构造函数中初始化。
capacity()和size()
基本的函数
size_t capacity() const
{
return _end_of_storage-_start;
}
size_t size() const
{
return _finish-_start;
}
capacity()函数返回空间大小,空间大小用最大空间地址_end_of_storage减去起始地址_start即可。
size()函数返回数组数据的数量,存放的数据到_finish结束,用_finish减去起始地址_start即可。
operator[ ]重载
T& operator[](size_t pos)
{
assert(pos<size());
return _start[pos];
}
const T& operator[](size_t pos) const//函数重载 给const类型用
{
assert(pos<size());
return _start[pos];
}
第一个重载用于普通数据,重载需要的参数为pos,即数组的下标位置,断言防止越界访问,返回的值是_start[pos],_start本身就是一个数组,可以用中括号[ ]访问数据。
第二个重载是专门给const类型使用,有时候数组中的数据如_start[0]需要传给一个函数,此时希望数组中的数据不被改变,需要加上const,这时const类型就能调用重载了。前面的const是用来限制返回值不被修改,后面的const是限制隐式参数this不被修改。
Tip:const成员调用非const函数是权限放大,无法调用,普通用户调用const是权限缩小
swap交换:
void swap(vector<T> &v)
{
std::swap(_start,v._start);
std::swap(_finish,v._finish);
std::swap(_end_of_storage,v._end_of_storage);
}
利用std库中的swap交换所有成员变量。
打印
void Print(const vector<int> v)
{
for(size_t i=0;i<v.size();++i)
{
cout << v[i] << " ";
}
cout << endl;
auto it=v.begin();
while(it<v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for(auto a:v)
{
cout << a << " ";
}
cout << endl;
}
当实现了迭代器iterator和operator[ ]重载后,我们就可以用v[i]和迭代器打印vector中的数据,当然自动遍历也因为底层和迭代器有关也可以使用。
构造函数初始化列表、析构、拷贝构造
初始化构造
无参构造:
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
初始化三个私有成员变量都为nullptr。
Tip:C++11后支持私有成员缺省赋值,可以不必在列表中初始化。
有参构造:
//vector(size_t n,const T& val=T()) //两个都写编译器会自动匹配
vector(int n,const T& val=T())
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
reserve(n);
for(size_t i=0;i<n;i++)
{
push_back(val);
}
}
先初始化三个私有成员变量后,再进行扩容,然后一次插入val值,完成n个val值的构造。
Tip:原本形参n是size_t类型,但是当传入的数据为int型时,会发生隐式转换,编译器会优先匹配下面的模版类型的拷贝构造,从而出错。
所以这里可以写两个一样,但形参n类型不一样的析构函数。
深拷贝构造:
传入迭代器来构造:
// [first, last)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
传入两个迭代器容器,用循环来依次把迭代器中的数据插入到迭代器指向位置。
v2(v1)拷贝构造:
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
//memcpy(_start, v._start, sizeof(T)*v.size());//浅拷贝
for (size_t i = 0; i < v.size(); ++i)
{
_start[i] = v._start[i];//vector<vector<int>>时,没写赋值重载,在这个地方是一个浅拷贝,需要再写一个赋值重载
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
当出现vector<vector>时是个二维数组,系统默认生成的赋值重载是第一个vector中的vector的地址浅拷贝,如下图:
所以我们需要写一个赋值重载来实现二维的深拷贝:
vector<T>& operator=(vector<T> v)
{
swap(v);//this和形参v交换,交换后的v因为是形参所以不会改变
return *this;
}
利用swap把形参v的数据交换过去,再返回*this即可。
我们写完赋值重载后,可以配合前面的“迭代器拷贝”来简单复写v2(v1)拷贝构造:
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(),v.end());//借助迭代器拷贝来把v中的数据全部拷贝进tmp中
swap(tmp);//tmp和this交换数据即可实现二维v2(v1)深拷贝
}
借助迭代器拷贝把拷贝数据全部放入tmp中,再用swap函数直接把tmp中的值交换进*this中。
析构函数:
~vector()
{
delete[] _start;
_start=_finish=_end_of_storage= nullptr;
}
析构函数没什么好说的,直接删除_start中数据,其余指针置nullptr即可。
迭代器实现
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
分为两种迭代器,一种是可以改变指向数据值的迭代器;一种是const类型迭代器,无法改变值。
reserve和resize两种扩容
Tip: 这两种扩容都不可以缩容
reserve扩容:
void reserve(size_t n)
{
if(n>capacity())//判断是否缩容
{
size_t sz=size();//储存扩容前的size大小
T* tmp=new T[n];
if(_start)//如果不为空
{
//memcpy(tmp,_start,sizeof(T)*size());
for (size_t i = 0; i < sz; ++i)
{
tmp[i]=_start[i];
}
delete[] _start;
}
_start=tmp;//此时_start地址已改变,直接用size()会导致_finish位置出错
_finish=_start + sz;//修改_finish正确指向
//_finish=_start + size();//_finish - _start 抵消剩_finish,且size()为负数
_end_of_storage=_start+n;
}
}
reserve扩容需要传入一个扩容大小的值n,当n比原来的容量大时才进行扩容操作,且reserve不接受缩容操作。
扩容不是原地扩容,函数中扩容借助了tmp的帮助来拷贝_start中的数据,然后改变_start指向来完成深拷贝。但是此时_start指向已经改变,和_finish的指向不匹配,不是一块空间中的地址,所以使用前面的sz来存储扩容前的有效数据大小,再和_start相加,即可得到正确的vector尾地址**_finish**。
**Tip:**这里本来用的是memcpy数据扩容,但是当vector中的数据为指针类型的例如string类型,memcpy是一种浅拷贝,所以这里采用for循环去依次赋值。
到此扩容才完整结束。
resize扩容:
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=val;
++_finish;
}
}
}
resize扩容也需要传入一个扩容大小的值n,但是resize扩容可以自定义扩容后的空间中的值:val,这个val值可以缺省,默认为匿名对象。它的扩容和reserve相同,只不过支持空间初始化和删除多余n个的数据。
当需要扩容的容量n小于原本的大小,那么就删除原本数据,直到留下n个数据;扩容的容量大于原本空间时,则初始化后面所有的值为val值。
Tip:匿名对象:
T();//此时生命周期只有这一行,因为没人会用它了 const T& xx=T();//当它赋值给const类型引用时会延长生命周期,生命周期跟随xx一起 ```
当匿名对象被const类型引用时就会延长生命周期了,延长到引用域结束。
vector的插入和删除
push_back尾插:
void push_back(const T& x)
{
if(_finish==_end_of_storage)
{
// this->reserve(capacity()== 0 ? 4 : capacity()*2);
reserve(capacity()== 0 ? 4 : capacity()*2);
}
*_finish=x;
++_finish;
}
尾插很简单,传入一个值x,再判断空间是否需要扩容,如果需要则扩容,然后在尾地址插入数据,后移尾地址_finish即可。
pop尾删
void pop()//尾删
{
assert(!empty());
--_finish;
}
bool empty()//判断是否为空
{
return _start==_finish;
}
需要断言是否为空,空的数组无法再删除,把_finish前移一位即可删除尾部数据,因为_finish中的数据被屏蔽,即完成尾删
insert插入
iterator insert(iterator pos,const T& val)
{
assert(pos>=_start);
assert(pos<=_finish);//可以在_finish处插入
if(_finish==_end_of_storage)//检查容量避免while越界移动数组
{
size_t len=pos-_start;//记录pos和_start的相对距离
//如果扩容,扩容后的_start地址改变,迭代器pos地址还是以前的地址,迭代器失效
reserve(capacity()== 0 ? 4 : capacity()*2);
pos=_start+len;//更新pos位置 解决pos迭代器失效 tips:pos还是一个指针类型的形参,这里改变,外面不改变且失效
}
iterator end=_finish-1;//_finish的位置是null值
while(end>=pos)
{
*(end+1)=*end;//移动数组
--end;
}
*pos=val;//pos位置插入val
++_finish;
return pos;
}
在pos位置插入val值,首先断言pos位置,不能超出这个vector数组。首先检查_finish位置,是否达到了空间最大值,如果_finish等于_end_of_storage,此时移动数组会越界访问,则需要扩容。
扩容时会遇到一个迭代器失效的问题(下面会说到),传入函数的pos值是未扩容前数组的地址位置,扩容之后,原先的_start已经被释放,所以pos是个野指针,要更新pos的位置,从而解决pos迭代器失效。
首先在扩容前记录原先pos和_start的相对距离存入len中,再执行扩容操作,最后更新pos位置,更新后的pos位置即是_start加上相对位置len。
空间问题解决完后,即可把pos后的数据往后移动,最后在pos位置插入val值,不要忘记++_finish移动尾地址。
最后返回pos以便外部继续使用这个pos位置的数据。
erase删除
iterator erase(iterator pos)
{
assert(pos<_finish);//_finish处没有元素不能删除
assert(pos>=_start);
iterator start=pos+1;
while(start!=_finish)//数据前移
{
*(start-1)=*start;
++start;
}
--_finish;//删除最后一个数据
return pos;//更新pos,返回pos值以便外部继续使用
}
指定pos位置数据删除,和insert一样先断言pos位置是否超出,不一样的一点是,这个pos值不能在_finish位置,因为_finish位置数据被屏蔽认为是空,不能删除。然后把pos后的位置前移到pos位置即可删除pos位的数据,不要忘记- -_finish,屏蔽最后一个值(即是删除)。
最后返回pos值方便外部继续调用这个pos位置的数据。
例如:
void test3()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it = v1.erase(it);
}
else
{
++it;
}
}
Print(v1);
}
在循环中,每次erase删除满足条件的值后,更新it的pos值,继续往后查找满足条件的值。
Tip:string中的erase使用迭代器同理。
*迭代器失效
外部迭代器失效问题:
auto pos=find(v1.begin(),v1.end(),3);//区间找3
if(pos!=v1.end())
{
v1.insert(pos,333);//pos位置插入333
}
//严格来说pos用过后失效了,不能再使用
//(*pos)++;
Print(v1);
pos= find(v1.begin(),v1.end(),333);
v1.erase(pos);
//erase后也失效,不要访问,行为结果为定义(和编译器有关)
pos是迭代器类型的变量,当我们使用find函数找到区间中的值后,赋值它的所在地址给pos,然后在pos位置insert插入一个值,执行完insert操作后,当内存空间未满,那么pos指向的是新插入数据的地址下标,即是333这个数据的地址,pos原本应是3所在的地址空间,它现在指向333,虽然可以对pos进行操作,但是会误认为这个数据是3,因为上面find的是3,不是333!
还有一种情况:插入数据时内存空间已经满了,insert会进行扩容。
扩容操作会导致里面形参pos的指针变量会因为扩容而改变地址,且原来的地址空间被delete释放了,但pos是一个指针类型的形参,外部的pos值不会改变。如果这个插入操作扩容了空间,那么这个pos是野指针,指向的是未知空间,它失效了,不能再使用了。
如果还需要使用pos,我们需要重新给它“找”一个新的数据地址。
当然erase操作后pos也会失效,不要再访问pos,使用pos的行为结果未定义,这个行为结果和编译器有关。
到此vecotr的基本功能和操作都已经实现,附上全部源代码。
#include <assert.h>
using namespace std;
namespace lty{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
{}
vector(size_t n,const T& val=T())
{
reserve(n);
for(size_t i=0;i<n;i++)
{
push_back(val);
}
}
// [first, last) 左必右开区间
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//v2(v1)深拷贝
// vector(const vector<T>& v)
// {
// _start = new T[v.capacity()];
// //memcpy(_start, v._start, sizeof(T)*v.size());//浅拷贝
// for (size_t i = 0; i < v.size(); ++i)
// {
// _start[i] = v._start[i];//当vector<vector<int>>时,是一个二维数组,没写赋值重载,是一个浅拷贝,需要再写赋值重载
// }
//
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
// }
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(),v.end());//借助迭代器拷贝来把v中的数据全部拷贝进tmp中
swap(tmp);//tmp和this交换数据即可实现二维v2(v1)深拷贝
}
vector<T>& operator=(vector<T> v)
{
swap(v);//this和形参v交换,交换后的v因为是形参所以不会改变
return *this;
}
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()
{
delete[] _start;
_start=_finish=_end_of_storage= nullptr;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _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=val;
++_finish;
}
}
}
void reserve(size_t n)
{
if(n>capacity())//判断是否缩容
{
size_t sz=size();//储存扩容前的size大小
T* tmp=new T[n];
if(_start)//如果不为空
{
// memcpy(tmp,_start,sizeof(T)*size());/当vector中的类型是string时是浅拷贝
for (size_t i = 0; i < sz; ++i)
{
tmp[i]=_start[i];
}
delete[] _start;
}
_start=tmp;//此时_start地址已改变,如果直接用size(),会导致_finish位置出错
_finish=_start + sz;
// _finish=_start + size();//_finish - _start 抵消剩_finish,且size()为负数
_end_of_storage=_start+n;
}
}
void push_back(const T& x)
{
if(_finish==_end_of_storage)
{
// this->reserve(capacity()== 0 ? 4 : capacity()*2);
reserve(capacity()== 0 ? 4 : capacity()*2);//this调用扩容
}
*_finish=x;
++_finish;
}
void pop()//尾删
{
assert(!empty());
--_finish;
}
void insert(iterator pos,const T& val)
{
assert(pos>=_start);
assert(pos<=_finish);//可以在_finish处插入
if(_finish==_end_of_storage)//检查容量避免while越界移动数组
{
size_t len=pos-_start;//记录pos和_start的相对距离
//如果扩容,扩容后的_start地址改变,迭代器pos地址还是以前的地址,迭代器失效
reserve(capacity()== 0 ? 4 : capacity()*2);
pos=_start+len;//更新pos位置 解决pos迭代器失效 tips:pos还是一个指针类型的形参,这里改变,外面不改变且失效
}
iterator end=_finish-1;//_finish的位置是null值
while(end>=pos)
{
*(end+1)=*end;//移动数组
--end;
}
*pos=val;//pos位置插入val
++_finish;
}
iterator erase(iterator pos)
{
assert(pos<_finish);//_finish处没有元素不能删除
assert(pos>=_start);
iterator start=pos+1;
while(start!=_finish)//数据前移
{
*(start-1)=*start;
++start;
}
--_finish;//删除最后一个数据
return pos;//返回pos值以便外部继续使用迭代器,解决迭代器失效问题
}
size_t capacity() const
{
return _end_of_storage-_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//函数重载 给const类型用
{
assert(pos<size());
return _start[pos];
}
bool empty()//判断是否为空
{
return _start==_finish;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
void Print(const vector<int> v)
{
// for(size_t i=0;i<v.size();++i)
// {
// cout << v[i] << " ";
// }
// cout << endl;
// auto it=v.begin();
// while(it<v.end())
// {
// cout << *it << " ";
// ++it;
// }
// cout << endl;
for(auto a:v)
{
cout << a << " ";
}
cout << endl;
}
//测试
void test1()//push_back尾插+resize
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
Print(v1);
v1.resize(10);
Print(v1);
}
void test2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
auto pos=find(v1.begin(),v1.end(),3);//区间找3
if(pos!=v1.end())
{
v1.insert(pos,333);//pos位置插入333
}
//严格来说pos用过后失效了,不能再使用
//(*pos)++;
Print(v1);
pos= find(v1.begin(),v1.end(),333);
v1.erase(pos);
//erase后也失效,不要访问,行为结果未定义(和编译器有关)
Print(v1);
}
void test3()//迭代器更新,解决迭代器pos失效问题
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it = v1.erase(it);
}
else
{
++it;
}
}
Print(v1);
}
void test4()
{
}
}