vector简介
在模拟实现vector之前,首先就得知道vector是个啥?vector是个啥呢?vector是一个stl里面的容器,并且是一个模板容器。它就像是一个顺序表模板。还记得顺序表吧?之前我实现的顺序表只能弄整形的数据,但是vector却可以弄几乎所有的数据。所以要实现vector便可以参考实现顺序表的功能来实现,并且还可以结合一下stl里面的源代码。
vector实现
1.vector的成员
在这里就得来参考一下stl里面的源代码了。看一看stl里面的vector成员是什么。源代码:
这便是vs2019下面的vector的成员,pointer便是typedef后的指针。但是这个指针可不是一般的指针,而是模板指针。来看一下:
vs2019下面的源代码复杂了,复杂到让我们这些小白看不懂。反正就记住vector的成员是下面三个便可以了:_start,_finish,_endofstorage。分别代表的意思就是:指向vector容器的开头,容器内内容的结尾,容器容量的结尾。那我们在实现时该怎么定义呢?我们可以像下面一样去定义:
namespace area{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endofstrage; }; }
1.首先搞一块area域来写自己的vector。
2.建立模板,并将模板的指针类型T* typedef为iterator,const T*定义为const_iterator。要在public的区域内。
3.再用typedef后的模板指针来定义成员。
2.vector初始化构造以及capacity(),size()函数
要使用一个对象,就得初始化一个对象。初始化对象应该用什么值呢?很明显,指针类型就得用空指针来初始化。初始化函数如下:
vector() :_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}
接下来便是写一个尾插函数,但是这个函数是有问题的。尾插函数如下:
void push_back( const T& x){int sz = size();if (_finish == _endofstorage){int cp = capacity() == 0 ? 4 : 2 * capacity();T* tmp = new T[cp];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[]_start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + cp;}*_finish = x;++_finish;}
3.reserve函数与resize函数
resever函数实现的是一个扩容的操作,resize函数实现的就是一个扩容加初始化的函数了。所以按照两者的功能便可以先实现reserve函数再实现resize函数。reserve函数实现代码如下:
void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}
有了reserve()函数接下来实现resize()函数就简单了。代码如下:
void resize(size_t n,const T& val = T()){reserve(n);//先检查是否扩容或者是否要开空间。iterator it = end();//将数据尾到容量尾这一段空间的元素初始化掉while (it != _endofstorage){*it = val;it++;}_finish += n;}
这里值的考究的便是在数据的结尾到容器的结尾到底该放什么数据呢?这里给的缺省值是
T()。这是一个匿名构造,构造出来的值是根据实例化出来的T的默认构造的值。这样便可以避免因为给死一个缺省值而导致的类型不匹配的错误。有了reserve()函数以后便可以对push_back()函数进行改良,改良如下:
void push_back( const T& x){if (capacity() == size()){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;++_finish;}
4,insert()函数与erase()函数
这两个函数实现的便是vector的中间插入与删除的功能。但是在这两个函数实现的过程中会出现迭代器失效的问题。
1.先来实现一下insert()函数,代码如下:
void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);reserve(size());//检查扩容iterator end = _finish;while (end != pos){*end = *(end - 1);end--;}*pos = val;_finish++;}
但是这个代码是有一丝丝问题的。比如当我插入以下数据时:
void test_vector3(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//尾插五个元素for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 22);//再中间插入三个元素v1.insert(v1.begin()+2, 66);v1.insert(v1.end(), 99);for (auto e : v1){cout << e << " ";}cout << endl;}
结果:
这样是可以正常的跑起来的。但是当我的数据变成:
void test_vector3(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 22);v1.insert(v1.begin()+2, 66);v1.insert(v1.end(), 99);v1.insert(v1.end(), 100);for (auto e : v1){cout << e << " ";}cout << endl;}
也就是再中间插入一个100时便会发生错误。错误如下:
为什么呢?其实这里是发生了迭代器失效的问题,从而引发了野指针的问题。为什么会发生迭代器失效呢?原因是发生了扩容。原来我的数据是这样的:
这个时候我就得发生扩容了吧,因为容量已经满了。我们的扩容是怎么扩的呢?是这样扩的:
T* tmp = new T[n];if (_start) { memcpy(tmp, _start, sizeof(T) * size()); delete[] _start;}_start = tmp; _finish = _start + sz; _endofstorage = _start + n;
原来的数据经过扩容操作以后就会变成:
但是我的pos是在那个位置呢?是在原来那个没有扩容的小空间上。为什么?因为我是在用begin()迭代器来完成插入操作。在调用begin()的时候,还没有扩容。所以pos的位置便在原来的那个已被销毁的空间上,所以pos变成了野指针。pos与_finish的位置关系如下图:
但是我的结束条件是这个:end!=pos,end一开始便是_finish。
iterator end = _finish; while (end != pos) {*end = *(end - 1);end--;}
所以end与pos是在两块不同的空间上的。说以这个条件是不会结束的。所以end会一直访问一些不该访问的空间。所以发生了错误。那我们该如何去修改呢?其实很简单,在扩容之前记录一下pos与_start的偏移量,在扩容后将pos的位置更新一下便可以了。
void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);int len = pos - _start;//记录偏移量reserve(size()+1);//插入一个数据后容量加1pos = _start + len;//更行positerator end = _finish;while (end != pos){*end = *(end - 1);end--;}*pos = val;_finish++;}
然后便可以正常运行了:
2.erase()函数,实现代码如下:
void erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator end = pos+1;while (end < _finish){*(end - 1) = *end;end++;}_finish--;}
在正常情况下:
void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e: v1){cout << e << " ";}cout << endl;v1.erase(v1.begin());v1.erase(v1.end());for (auto e : v1){cout << e << " ";}cout << endl;}
这是可以正常使用的:
但是在某些特殊的场景之下这就会发生迭代器失效的情况。比如我要将一列数据中的偶数给删除时。在以下情况下,用我写的erase代码的情况如下:
1。偶数不在尾部且偶数不连续:
void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>:: iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;}
运行结果是正常的:
2.偶数有连续的情况:
void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(2);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>:: iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;}
运行结果是有错误的:结果不对
3.当尾部的数据是偶数时:程序崩溃
void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);v1.push_back(8);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>:: iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;} }
结果:
这是为什么呢?当数据内有连续偶数时删不干净其实就是我们的操作逻辑写错了。当要删除元素是我们不应该++it而是应该让it保持不动,对移动后的数据还得来一次判断。所以我们要将删除逻辑改为这样:
void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(2);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>:: iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);//删除后不应该++,应改再对这个位置上的新数据进行判断}else{it++//不是偶数时再++}}for (auto e : v1){cout << e << " ";}cout << endl;}
结果:
对于尾巴上是偶数的删除,结果也是对的:
这里的迭代器失效问题就是迭代器位置失去意义。
5.深拷贝与浅拷贝问题(大坑)
先来写一个拷贝构造函数:
vector( vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){T* tmp = new T[v.capacity()];if (v._start){memcpy(tmp, v._start, sizeof(T) * v.size());}_start = tmp;_finish = tmp + v.size();_endofstorage = tmp + v.capacity();}
来个整型试试看:
void test_vector6(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int>v2(v1);for (auto e : v2){cout << e << " ";}cout << endl;}
结果:
程序正常运行。
再来一个string:
void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");//v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;}
只插入四个的话,程序正常运行:
但是,当我要插入第五个元素时:
void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;}
程序崩掉了:
为啥呢?其实还是因为扩容,也就是reserve()函数。再来看看reserve()函数的实现:
void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}
这里拷贝数据的方式是memcpy()。拷贝完了以后就要将_start里的数据给释放掉。但是memcpy函数实现的是浅拷贝,而string对象里面可是有指针的。这样的话,在释放掉原来的空间后再调用析构函数就会对同一块空间进行两次释放导致报错。该怎么改呢?调用深拷贝就行了。改进如下:
void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){for (int i = 0;i < size();i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}
为什么这样就可以了呢?这是因为自定义类型的=实现的就是深拷贝。
再来调用vector的拷贝构造:
void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout<<endl; vector<string>v2(v1);//拷贝构造for (auto e : v2){cout << e << " ";}cout << endl;}
运行:出错
其实这里的原因还是因为memcpy()浅拷贝导致的对同一块空间析构两次导致的错误。所以这里要将拷贝构造函数进行改造,改造如下:
vector( vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto e : v){push_back(e);}}
然后我们的程序便可以跑起来了。
结果:
6.其它的构造函数
1.迭代器区间构造函数。
作用:通过某个类型的容器的迭代器来初始化。代码如下:
template<class InputIterator>//在类里面定义一个模板,这样就可以让这个迭代器区间构造的函数更加方便使用 vector(InputIterator first, InputIterator end){while (first != end){push_back(*first);first++;}}
现在尝试通过下面的代码来进行测试:
void test_vector8(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;vector<string>v2(v1.begin(), v1.end());for (auto e : v2){cout << e << " ";}cout << endl;}
结果:正常
2.指定大小和初始化值的函数
vector(int n, const T& val = T()){reserve(n);for (int i = 0;i < n;i++){push_back(val);}}
这里需要注意一点,n的类型得是int才行。要不然在调用这个函数来构造一个vector<int>类型的对象时就会发生类型匹配上的错误。