通过上篇文章,我们知道vector的接口实际上和string是差不多的,但是他俩的内部结构却大不一样,vector内有三个成员变量:_start、_finish、_endofstorage:
_start指向容器的头元素,_finish指向有效元素末尾的元素,_endofstorage指向整个容器的尾。
而vector的迭代器就是typedef T*,也就是元素的指针。
所以我们在模拟之前就可以先做出一个小框架,为了不与库中的vector冲突,我们把他放在我们自己的命名空间中:
namespace CYF
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;//指向容器的头iterator _finish;//指向有效数据的尾iterator _endofstorage;//指向容器的尾};
}
构造函数
构造函数1
这个是无参构造函数,我们将他所有的成员对象都制成空指针就行:
//构造函数1vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}
构造函数2
vector还支持使用一段迭代区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说函数接收的迭代器的类型是不确定的,所以我们这里需要将构造函数设计为一个函数模板,在函数体内将该迭代区间的数据一个个尾插到容器当中即可:
//构造函数2template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中{push_back(*first);first++;}}
构造函数3
vector还支持构造一种初始化有n个值为val的元素的对象。我们可以先使用reserve函数将容器的容量设为n,然后尾插n个值为val的数据到该对象即可:
vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低for (size_t i = 0; i < n; i++)//尾插n个值为val的元素{push_back(val);}}
但是这个函数需要再实现两个重载函数:
vector(long n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n); for (size_t i = 0; i < n; i++) {push_back(val);}
}
vector(int n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n); for (int i = 0; i < n; i++) {push_back(val);}
}
这两个重载函数和他不同的就是第一个参数n的类型,如果没有这两个重载函数的话,我们调用下面的代码时,编译器就会优先和构造函数2匹配。
vector<int> v1(1, 3); //这里调用的是构造函数2
并且因为构造函数2当中对两个参数进行了解引用(即将int类型解引用)报错:
补充:若我们知道需要多少空间,我们最好先使用reserve函数一次性将空间开辟好,避免调用push_back函数时再增容,导致效率降低。
拷贝构造函数
传统写法1
这里仍然需要深拷贝,先开一段与拷贝对象大小相等的空间,然后将该对象内的元素一个个拷贝过来即可,最后记得更新成员对象的值:
拷贝构造(传统写法1)vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy{_start[i] = v[i];}_finish = _satrt + v.size();//有效数据的尾_endofstorage = _start + v.capacity();//整个容器的尾}
注意我们这里数据需要一个个拷贝过来,不能使用memcpy函数。若vector内部存储的是内置类型或者无需深拷贝的自定义类型时,可以使用memcpy函数,但是当vector内存储的数据是需要深拷贝的自定义类型的时候,使用memcpy就不行了。例如:我们拿存储的数据类型是string举例:
如图:我们vector对象内元素都是string类型,每个元素都指向自己的一个字符串。若我们使用memcpy函数的话,就会出现下面的情况:
memcpy会将每个string元素内成员变量的值原封不动地拷贝过去,也就导致了两个vector对象内的元素都指向同一组string元素。最后析构环节也就会导致内存错误。
而我们拷贝时采用string类的operator=的方式,采用的是深拷贝,最终的结果是这样:
就不会导致内存问题了。
传统写法2
这里我们直接调用reserve函数进行空间开辟,而后通过范围for将原有对象一个个尾插:
//拷贝构造(传统写法2)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (const auto& e : v){push_back(e);}}
现代写法
这里我们将v1作为临时对象,通过构造函数构造出来。而后将v1与*this互换即可,经典的现代写法,无需多言:
//拷贝构造(现代写法)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector<T>v1(v.begin(),v.end());swap(v1);}
赋值运算符重载
传统写法
先判断是否为自己给自己赋值,若给自己赋值则不用做任何操作。若不是,则:先开辟一块和要拷贝对象capacity大小一样的空间,然后将数据一个个拷过来,最后更新成员对象的值:
//赋值运算符重载函数(传统写法)vector<T>& operator=(const vector<T>& v){if (this != &v){delete[]_start;_start = new T[v.capacity()];_finish = _start + v.size();_endofstorage = _satrt + v.capacity();for (const auto& e : v){push_back(e);}}return *this;}
现代写法
operator=的现代写法很巧妙,右值传参的时候使用传值传参而不是引用传参,这样就直接调用了vector的拷贝构造函数,然后用这个拷贝出来的局部对象和左值进行交换,就相当于做了赋值操作,而v是局部变量,在函数结束时会自动调用析构函数销毁。
//现代写法
vector<T>& operator=(vector<T> v)
{swap(v); return *this; //支持连续赋值
}
析构函数
先判断是否为空指针,若不是,将空间释放,再将成员变量全都置为nullptr:
//析构函数~vector(){delete[]_start;_start = nullptr;_finish = nullptr;_endofstorage = nullptr;}
迭代器相关的函数(begin&&end)
我们上面讲过,vector的迭代器实际上就相当于存储数据类型的指针,begin()就是返回第一个元素地址,end()就是返回最后一个元素的下一个数据的地址:
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;}
所以我们使用迭代器遍历vector对象实际上也就是使用指针遍历。并且实现了迭代器后,也就可以使用范围for,因为范围for在编译的时候,会自动替换为迭代器的形式。
关于容量和大小的函数
size && capacity
这里的size和capacity的算法跟string不同,string内直接就有两个成员变量表示size和capacity,而vector这里需要用_finish - _start和_endofstorage - _start分别得到:
size_t size()const{return _finish - _start;}size_t capacity()const{return _endofstorage - _start;}
reserve
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,不用做任何操作
我们先判断n是否大于容量,若大于,就开辟一块容量为n的空间,将原来容器中的有效数据拷贝进去,再将原来的空间释放,记得更新容器内的成员变量:
void reserve(size_t n)
{if (n > capacity()) {size_t sz = size(); //记录当前容器当中有效数据的个数T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间if (_start) //判断是否为空容器{for (size_t i = 0; i < sz; i++) //拷贝数据{tmp[i] = _start[i];}delete[] _start; //释放原有空间}_start = tmp; //更新成员对象_finish = _start + sz; _endofstorage = _start + n; }
}
我们这里要注意要先记录元素的个数,否则我们最后更新_finish指针指向的时候,若调用size()函数,而size()函数是通过_finish-_start得到的,而此时_start已经指针指向已经改变,我们就会得到一个随机值,所以我们需要提前记录。
resize
1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
2、当n小于当前的size时,将size缩小到n。
我们需要先判断n是否小于size,若小于,将_finish改变即可,若不小于,还需要判断是否需要增容,再将扩大的数据赋值为val即可:
void resize(size_t n, const T& val = T()){if (n < size()) //当n小于size时{_finish = _start + n; }else {if (n > capacity()) {reserve(n);}while (_finish < _start + n) {*_finish = val;_finish++;}}}
在C++中内置类型也可以看作是一个类,也有自己的默认构造函数,所以val的缺省参数可以设为T()临时对象。
empty
判断对象是否为空:
bool empty()const{return _start == _finish;}
有关修改对象的函数
push_back
先判断是否需要增容,再尾插,最后更新_finish的指向:
void push_back(const T& x){if (_finish==_endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish = x;_finish++;}
pop_back
判断对象是否为空,不为空将_finish--即可:
void pop_back(){assert(!empty());//容器不能为空_finish--;}
insert
我们需要先判断pos是否合法,再判断是否需要增容,然后将pos后的元素都向后移一位,将x插在pos的位置,最后将_finish++:
void insert(iterator pos, const T& x){if (pos <= _finish)//判断pos是合法{size_t n = pos - _start;//记录pos和_start之间的间隔if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}pos = _start + n;//根据记录的间隔数算出positerator i = _finish;while (i != pos){*(i + 1) = *i;i--;}*pos = x;_finish++;}}
提示:这里我们需要提前记录pos和_start之间的间隔,通过间隔确定增容后的pos的指向,否则pos还是指向原来已经被释放的空间。(我自己在写的时候就出现了这样的错误)
erase
先判断对象不能为空,删除时将pos后面的数据统一前移一位,覆盖掉pos的数据,返回值还为pos,即删除元素的下一个元素的位置:
iterator erase(iterator pos){assert(!empty());iterator it = pos;while (it != _finish){*it = *(it+1);it++;}_finish--;return pos;}
swap
我们调用库中的swap函数将两个对象中的成员变量交换即可(这样就相当于交换了两个对象内部指针的指向,不耗费太多资源,若我们直接调用swap交换两个对象,若遇见例如string这样的对象,swap交换时做的全都是深拷贝操作,太耗费资源了,所以我们只交话两个对象内部的成员变量即可):
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}
operator[ ]
通过下标访问vector内元素,直接返回数据即可:
T& operator[](size_t i){if(i<size()){return *(_start + i);}}const T& operator[](size_t i)const{if (i < size()){return *(_start + i);}}
最后贴上整体代码:
#pragma once
#include<iostream>
#include<cassert>namespace CYF
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//构造函数1vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}//构造函数2template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中{push_back(*first);first++;}}//构造函数3vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低for (size_t i = 0; i < n; i++)//尾插n个值为val的元素{push_back(val);}}//拷贝构造(传统写法1)//vector(const vector<T>& v)// :_start(nullptr)// ,_finish(nullptr)// ,_endofstorage(nullptr)//{// _start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间// for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy// {// _start[i] = v[i];// }// _finish = _satrt + v.size();//有效数据的尾// _endofstorage = _start + v.capacity();//整个容器的尾//}//拷贝构造(传统写法2)//vector(const vector<T>& v)// :_start(nullptr)// , _finish(nullptr)// , _endofstorage(nullptr)//{// reserve(v.capacity());// for (const auto& e : v)// {// push_back(e);// }//}//拷贝构造(现代写法)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector<T>v1(v.begin(),v.end());swap(v1);}赋值运算符重载函数(传统写法)//vector<T>& operator=(const vector<T>& v)//{// if (this != &v)// {// delete[]_start;// _start = new T[v.capacity()];// _finish = _start + v.size();// _endofstorage = _satrt + v.capacity();// for (const auto& e : v)// {// push_back(e);// }// }// return *this;//}//赋值运算符重载函数(现代写法)vector<T>& operator=(const vector<T>& v){if (this != &v){vector<T> v1(v.begin(),v.end());swap(v1);}return *this;}//析构函数~vector(){delete[]_start;_start = nullptr;_finish = nullptr;_endofstorage = nullptr;}//迭代器相关函数iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}//容量和大小相关函数size_t size()const{return _finish - _start;}size_t capacity()const{return _endofstorage - _start;}void reserve(size_t n){if (n >= capacity()){iterator _start_ = new T[n];_finish = _start_ + size();_endofstorage = _start_ + n;size_t i = 0;while (_start_ + i != _finish){_start_[i] = _start[i];i++;}delete[]_start;_start = _start_;_start_ = nullptr;}}//void resize(size_t n, const T& val = T())//{// if (n <= size())// {// _finish = _start + n - 1;//这时候只会size,不会改变capacity// }// else if (n > size() && n <= capacity())// {// size_t more = n - size();// while (more > 0)// {// *_finish = val;// _finish++;// more--;// }// }// else// {// reserve(n);// while (_finish != _start + n)// {// *_finish = val;// _finish++;// }// }//}void resize(size_t n, const T& val = T()){if (n < size()) //当n小于当前的size时{_finish = _start + n; //将size缩小到n}else //当n大于当前的size时{if (n > capacity()) //判断是否需要增容{reserve(n);}while (_finish < _start + n) //将size扩大到n{*_finish = val;_finish++;}}}bool empty()const{return _start == _finish;}//修改容器内容相关函数void push_back(const T& x){if (_finish==_endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish = x;_finish++;}void pop_back(){assert(!empty());//容器不能为空_finish--;}void insert(iterator pos, const T& x){if (pos <= _finish)//判断pos是合法{size_t n = pos - _start;if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}pos = _start + n;iterator i = _finish;while (i != pos){*(i + 1) = *i;i--;}*pos = x;_finish++;}}iterator erase(iterator pos){assert(!empty());iterator it = pos;while (it != _finish){*it = *(it+1);it++;}_finish--;return pos;}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//访问容器相关函数T& operator[](size_t i){if(i<size()){return *(_start + i);}}const T& operator[](size_t i)const{if (i < size()){return *(_start + i);}}private:iterator _start;//指向容器的头iterator _finish;//指向有效数据的尾iterator _endofstorage;//指向容器的尾};
}
以上就是这篇文章的全部内容,谢谢大家!!