vector各个接口函数
//构造函数
vector()
vector(size_t n,const T& val=T())
vector(int n,const T& val = T())
//拷贝构造函数
vector(const vector<T>& v)
//迭代器版本的
vector(inputiterator first, inputiterator end)
//赋值运算符重载
vector<T>& operator=( vector<T> v)
//析构函数
~vector()
//容量相关的函数
void reserve(size_t n)//扩容版本
void resize(size_t n, T val = T())//扩容+初始化版本
size_t size()const//数量
size_t capacity() const容量
//插入修改容器中的数据的函数
void push_back(T x)
iterator insert(iterator pos, const T& val)
iterator erase(iterator pos)
迭代器相关的函数
iterator begin()//指向首元素
iterator end()//指向最后一个元素的下一个位置
const_iterator begin()const
const_iterator end()const
//容器中数据访问的[]
T& operator[](size_t pos)
const T& operator[](size_t pos)const
vector成员中相关成员变量
vector本质上就是一个动态开辟的数组,里面的成员跟以前的顺序表没啥本质区别,但是vector是用模板实现的,可以任意类型的数据。我们也可以看看源码中vector是怎么实现的呢?
分别使用了一个迭代器的指针,一个表示首元素,一个表示最后一个元素的下一个位置,还有一个就是end_of_storage表示容量。我在下面的写的vector当中给了缺省值,避免每次写构造函数的时候都要初始化一下。
private:
iterator _start=nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
默认成员函数
我给了缺省值,会自动走初始化列表,因此我在这边不要在初始化一下了。
vector()
{}
构造函数的实现
vector支持一种这样的构造,你可以给出n个你想初始化的值,在l类与对象那么我们知道构造函数对内置类型不做处理,对自定义类型会去调用它的构造函数。但是C++11以后对内置类型也会做处理了。int x= int(); char c=char();
vector(size_t n,const T& val=T())//c++11之后支持对内置类型也会初始化
{if (size() + n > capacity()){reserve(size() + n);}while ((int)n--)//会发生隐式类型的转化{push_back(val);}_finish += n;}
我上面还写了一个int类型的构造这边就不放了,主要是模板会自动匹配它的最优选择,我们一般给vector v(10,1);会自动隐式类型转换成int int 的类型。
拷贝构造函数
写法思路就是_start先开辟一块与该容器大小相等的空间,然后将该容器的数据一个一个的拷贝过来,最后改变一下_finish跟_end_of_storage
vector(const vector<T>& v)
{std::cout << "vector(const T& v)" << std::endl;_start = new T[v.capacity()];//memcpy(_start, v._start, v.size()*sizeof(T));for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}
赋值运算符重载的函数
传统写法
思路就是开辟一个与容器大小相同的tmp空间,然后将容器中数据传给tmp数组,思路跟拷贝构造后面一致。
//传统写法
vector<T>& operator=(vector<T>& v)
{if (this != &v){iterator tmp = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){tmp[i] = v._start[i];}_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}
现代写法
就是通过算法库里面的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);
}
vector<T>& operator=( vector<T> v){Swap(v);return *this;}
迭代器版本的构造函数
vector还支持使用一段迭代器区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板,在函数体内将该迭代器区间的数据一个个尾插到容器当中即可。
template<class inputiterator>
//迭代器版本默认是左闭右开的
vector(inputiterator first, inputiterator end)
{while (first != end){push_back(*first);first++;}
}
析构函数
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
容量相关的函数
size_t size() const
{return _finish - _start;
}
size_t capacity() const
{return _end_of_storage - _start;
}
reserve的实现规则就是判断当前的是否达到最大的容积,达到就扩容,开辟一块tmp空间,当_start不为空的时候就开始挪动数据。
void reserve(size_t n)
{if (n > capacity()){iterator tmp = new T[n];//挪动数据if (_start){size_t sz = size();//memcpy(tmp, _start, sizeof(T) * sz);//浅拷贝for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];//实现深拷贝}}delete[] _start;_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}
resize函数实现的思路就是当输入n小于当前内存的时候我们就不需要初始化,并且跟新一下_finish=_start+n,,当大于的时候就需要分俩种情况,一种是当vector为空的时候,我们就直接在头部开始插入数据,第二种就是本来就有数据,需要尾插数据一直达到n个大小。
void resize(size_t n, T val = T())
{if (n <=size()){_finish =_start+n;}else{reserve(n);while ((int)n--){if (_start){push_back(val);}else{insert(begin(), val);}}}
}
插入修改容器中的数据的函数
尾插数据push_back,实现思路就是插入数据首先就是扩容,然后在finish后面插入数据,然后让finish++一下。
void push_back(T x)
{if (_finish >= _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;_finish++;
}
insert()的写法,以前的写法就是给一个下标pos的位置,然后删除对应pos位置上的数据,而现在这个pos变成了指针,我们在使用指针插入对应数据的时候,特别容易出现迭代器失效的情况,也就是野指针的行为,为啥呢?因此我们在插入数据的时候需要扩容,我们是额外开了一个数组,然后就会将原数组释放掉,再让_start指向新开辟的数组,所以我们需要保存pos指针的位置。
iterator insert(iterator pos, const T& val)
{assert(pos <=_finish);size_t len = pos - _start;if (_finish >= _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}iterator end = _finish - 1;pos = _start + len;//跟新pos指针的位置while (end >= pos){*(end + 1) = *end;end--;}*pos = val;_finish++;return pos;
}
erase的实现,在Linux跟vs中对erase()底层实现都有区别,vs中只要删除了该数据,该位置的值就不可访问或者解引用修改查看,而inux环境下就宽松了很多,我们可以随意的访问或者修改,但是出错了也意味着不容易判断。
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it !=_finish){*(it - 1) = *it;it++;}_finish--;return pos;}
迭代器相关的函数和数据访问
这个很简单直接放代码
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const_iterator begin()const
{return _start;
}
const_iterator end()const
{return _finish;
}
T& operator[](size_t pos)
{assert(pos <= size());return _start[pos];
}
const T& operator[](size_t pos)const
{assert(pos <= size());return _start[pos];
}