目录
一、vector的大致框架
二、vector的模拟实现
构造函数
默认构造函数
使用迭代器初始化的构造函数
为什么这里的迭代器区间取名叫InputIterator呢?
拷贝构造函数
传统写法
现代写法
operator=的实现
现代写法
size()的实现
capacity()的实现
operator [ ] 的实现
push_back()的实现
方法一:提前将size算出来。再去更新_start
方法二:提前将_finish算出来,注意_finish应该是tmp+size();
关于tmp用不用手动delete的问题
迭代器的实现
reserve的实现
resize的实现
const引用延长生命周期
resize的实现
pop_back的实现
insert的实现
迭代器失效
erase的实现
迭代器失效
memcpy的问题
三、完整代码
vector源代码的大致框架,vector本质就是一个模板类
vector的成员变量不再是我们熟悉的size,capacity,而是变成了功能一致的三个指针
大体框架
namespace pxl
{
template
class vector
{
public:
typedef T* iterator;
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
(一个类模板的成员函数,又可以是一个函数模板)
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
如果不用这个函数模板,意思就是只能使用vector的迭代器进行初始化,而不能使用其他类型的迭代器进行初始化,所以要用到一个模板,可以传任意类型的迭代器。
函数模板的模板参数要传迭代器区间时,是存在命名规范的。
迭代器是用来访问容器的,产生这么多不同类型的迭代器在于容器有不同类型的结构。
这些迭代器从下到上属于下面包含上面的关系
如果函数参数是随机迭代器,那么实参就只能传随机迭代器。
如果函数参数是单向迭代器,那么实参就可以传单向迭代器,双向迭代器,随机迭代器。
如果函数参数是只写迭代器,那么实参就可以传只写迭代器,只读迭代器,单向迭代器,双向迭代器,随机迭代器。
eg:以sort为例,它是只能传随机迭代器的
#include
#include
#include
#include
using namespace std;
int main()
{
pxl::test2();
vector v;
v.push_back(5);
v.push_back(4);
v.push_back(3);
v.push_back(2);
v.push_back(1);
sort(v.begin(), v.end());
list lt;
lt.push_back(5);
lt.push_back(4);
lt.push_back(3);
lt.push_back(2);
lt.push_back(1);
sort(lt.begin(), lt.end());
}
对于上面这段代码,语法上是没有任何问题的,因为迭代器是一个模板,可以传任何类型,但是sort的底层是快排,快排需要三数取中需要用到 - 操作 但是list的迭代器不满足-操作,所以最终要报错。因为vector底层是一个连续的数组,而list底层是一个链表,所以vector支持减操作,list不支持减操作。
以v2(v1)为例,开一块和v1一样大的空间,再把v1的数据拷贝到这块空间上去
vector(const vector& v)
{
_start = new T[v.capacity()];
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
memcpy(_start, v._start, v.size() * sizeof(T));
}
借助迭代器初始化的构造函数,用v1创建一个临时变量tmp,再将v2与tmp交换,tmp出了作用域调用析构函数销毁
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//现代写法
vector(const vector& v)
{
vector tmp(v.begin(), v.end());
/*swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);*/
swap(tmp);
}
以v3=v1为例,利用传值传参拷贝构造v1,此刻的v就是v1,然后将v3与v1交换,v是临时对象出了作用域就会调用析构函数,析构掉v1
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector& operator=(const vector v)
{
/*swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);*/
swap(v);
return *this;
}
观察上图,易得:
size_t size() const
{
return _finsih - _start;
}
观察上图,易得:
size_t capacity() const
{
return _endofstorage - _start;
}
T& operator[](size_t i)
{
assert(i < size());
return _start[i]; //指针充当数组
}
T代表模板,_start的类型是T*,所以返回值是T&
const版本只能读不能写,同时适合const对象传参
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i]; //指针充当数组
}
void push_back(const T&x)
{
if (_finish == _endofstorage)
{
size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
T* tmp = new T[Newcapacity];
if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
}
*_finish = x;
++_finish;
}
思路看似没有问题,但是调试过后发现代码崩溃了,原因就是_finish与_endofstorage是空指针
测试样例:
默认构造
所以我们就需要在最开始的时候通过tmp算出来_finish与_endofstorage、但我们加上以后还会有问题
原因就是我们实现的size是_finish - _start;,两个一抵消,所以_finish永远是0(一个空指针).
解决方法:
void push_back(const T&x)
{
if (_finish == _endofstorage)
{
size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
size_t sz = size(); //提前算出size(),避免出错
T* tmp = new T[Newcapacity];
if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + Newcapacity;
}
*_finish = x;
++_finish;
}
void push_back(const T&x)
{
if (_finish == _endofstorage)
{
size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
T* tmp = new T[Newcapacity];
if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_finish = tmp + size();
_start = tmp;
_endofstorage = _start + Newcapacity;
}
*_finish = x;
++_finish;
}
更改后,监视的过程
tmp是不需要我们手动在push_back内部进行delete的。如果tmp是内置类型,tmp出了作用域后不作任何处理,但是我们已经将tmp赋值给了_start,我们又实现了vector的析构函数,析构函数里又会释放这个_start,所以等到vector对象的生命周期结束的时候,vector就会自动释放_start,从而释放了tmp,因为它俩指向的都是同一块空间。如果tmp是自定义类型,tmp除了作用域会自动调用它的析构函数,进行释放。所以是不需要我们在push_back内部手动释放的。
对于vector来说就是原生指针
typedef 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;
}
如果输入的容量大于capacity就进行扩容
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());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
实现完reserve后push_bcak就可以进行复用
void push_back(const T&x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
这的缺省值不能给0,因为T可能是多种类型,如果是int可以给0,但是其他类型如string等就不能给0了,这里我们给一个T() ,如果是int就是0,int*就是空指针,如果是string就是string的匿名对象,调用string的默认构造函数。 匿名对象的生命周期只在当前这一行但是用const引用修饰以后就会延长它的生命周期 。模板出现以后可以认为内置类型也有默认构造函数
关于const引用可以延长生命周期可以通过以下例子进行验证
class A { public: A() {} ~A() { cout << "~A()"; } }; void _test() { A(); //过了这一行就会调用析构函数 const A& x = A(); //出了这个函数作用域才回去调用析构函数 }
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
只要--_finish即可,但是也不能一直减_finish,当_finsih==_start的时候已经说明没数据了。
void pop_back()
{
assert(_finsih > _start);
--_finish;
}
在指定pos位置进行插入
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
}
eg1:经过这次验证也确实莫得问题
本质就是迭代器失效
原因就是eg1中只有3个数据,这时候的总空间是4(我们的代码默认第一次开4个空间),所以用我们当前的逻辑不会出错,但是eg2中有4个数据,在插入的时候就需要进行扩容,而扩容会新开一段空间, 这时候_start和_finish都改变了指向。
这两段空间pos是可以访问到的,当把一块内存释放了以后是将它还给系统了(将它的使用权还给系统了),此时这个pos还指向一块被释放的空间,在迭代器的角度,这个迭代器就失效了,然后在解引用pos就是野指针的访问。
解决方法法:
对这个pos进行更新,提前算好pos与_start的位置,在扩容的时候更新pos
if (_finish == _endofstorage)
{
//扩容会导致pos的失效,扩容前需要更新一下pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
但是此时的问题还没有完全解决,insert内部的pos的失效我们已经解决,但是我们调用insert时传的实参it(为了区分我们用it代替pos)也存在迭代器失效的问题
这里的原因就是因为insert的实现是传值返回,形参的改变不会影响实参。如果insert中发生了扩容,那么会导致it指向空间被释放,it本质就是一个野指针,这种问题,我们也叫做迭代器失效.
解决方法:
用一个返回值接收新的位置的pos
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
//扩容会导致pos的失效,扩容前需要更新一下pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
return pos;
}
通过接收返回值解决it失效的问题
注意这里不能传引用解决,因为会导致很多的问题
如果传引用,这种插入是不可行的,因为beign()返回的是临时对象,临时对象具有常性。那么传const引用呢?这也是不行的,因为insert内部的pos在扩容的时候是需要改变的,所以不能传引用。
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
}
同样erase也会存在迭代器失效的问题 。
现在我们实现要求删除v中所有的偶数
1.0 v中数据是1 2 3 4 崩溃
最后一个数是偶数,会导致erase以后,it意义改变,再++一下,导致it和end结束判断而错过。
那能否改为小于解决这个问题呢?
while(it < v.end())
{
//...
}
答案也是不行的因为这不符合迭代器的使用规则,不对任何容器的迭代器都通用。
2.0 v中数据是1 2 3 4 5正常
3.0 v中数据是1 2 4 5会没删完
erase(it)以后,it指向位置的意义就已经改变了。直接++it可能会导致一些意料之外的结果。如果是连续的偶数,会导致后一个偶数没有判断,没有被删掉。再其次,erase删除有些vector版本的实现,不排除他会缩容,如果是这样erase(it) 以后it也可能是野指针,跟insert类似。ps:SGI和PJ版本的vector都没有这样做
解决方法:
导致上述三种问题,本质都是erase(it)以后it的意义改变了,再去++it是不对的。其实就是it失效了
对erase增加一个返回值,返回删除后pos的位置
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
这部分判断改为这样,就可以解决这个问题
PS:写成这样同样是无法解决问题的
总结:vector的迭代器失效主要发生在insert和erase,只要使用迭代器访问访问的容器,都可能存在迭代器失效,string失效的场景与vector完全一样,但是string很少失效,因为它的插入与删除主要使用的是下标插入的重载函数,很少用迭代器进行插入与删除。
对于这段代码是没有问题的
但是如果多插入一个,程序就崩溃了。
目前版本的push_back拷贝用的是memcpy
这是因为memcpy是一个浅拷贝,对于内置类型没问题,但对于自定义类型的string就会发生同一块空间被析构函数释放两次的问题。
对于内置类型,tmp出了作用域不做任何处理;对于自定义类型,tmp出了作用域会调用它的析构函数。delete[ ] _start析构了一次自定义类型,tmp出了作用域也要析构一次自定义类型,所以就造成了一次空间被释放了两次。所以最终程序崩溃。
解决方法:
不用memcpy浅拷贝,而是进行深拷贝
void reserve(size_t n)
{
if (n>capacity())
{
size_t sz = size(); //提前算出size(),避免出错
T* tmp = new T[n];
if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
{
for (size_t i = 0; i < sz; i++)
{
//T是int,一个一个拷贝没问题
//T是自定义类型 ,一个一个拷贝调用的是T的深拷贝赋值=
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void push_back(const T&x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
namespace pxl
{
template
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//传统写法的拷贝构造
/* vector(const vector& v)
{
_start = new T[v.capacity()];
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
memcpy(_start, v._start, v.size() * sizeof(T));
//这里的memcpy和reserve一样
}*/
//迭代器区间构造
//一个类模板的成员函数,又可以是一个函数模板
template //为什么叫inputiterator,迭代器区间的引擎规范
//函数模板的模板参数要传送迭代器区间,存在命名规范
//input_ 只写迭代器 output_ iterator只读迭代器 forward_ 单向迭代器(单链表) bidirectional_双向迭代器
//randomaccess_ 随机迭代器
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._start);
std::swap(_endofstorage, v._endofstorage);
}
现代写法的拷贝构造
vector(const vector& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector tmp(v.begin(), v.end());
//this->swap(tmp);
swap(tmp);
}
//opreator
vector& operator = (vector v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
/* size_t newCapacity =( capacity() == 0 ? 4 : capacity() * 2);
size_t sz = size();
T* tmp = new T[newCapacity];
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + newCapacity;*/
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//使memcpy对自定义类型又浅拷贝的问题
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; i++)
{
//T是int,一个一个拷贝没问题
//T是自定义类型 ,一个一个拷贝调用的是T的深拷贝赋值=
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
//const引用,延长匿名对象的生命周期,匿名对象传缺省值,T这个类型生成的匿名对象,
//模板出现以后可以认为内置类型也有构造函数
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
void pop_back()
{
assert(_finish > _start);
--_finish;
}
//vector的迭代器失效主要发生在insert和erase
//只要使用迭代器访问访问的容器,都可能迭代器失效
//string失效的场景与vector完全一样,但是string很少失效,因为它的插入与删除使用下标
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish); //=是为了尾插
//满了就扩容
if (_finish == _endofstorage)
{
//扩容会导致pos失效,扩容需要更新一下pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
--end;
}
*pos = x;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
void test()
{
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector v2(v);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i];
}
cout << endl;
for (auto e : v)
{
cout << e;
}
}
}