博主的博客主页——>点击这里
博主的gitee主页——>点击这里
文章目录
前言
上一篇文章我带大家了解了我们STL库中的容器string,这期我们接着来学习下一个容器vector。
接口部分较为简单,大家自行查阅文档即可,本篇文章主要是讲解底层实现原理,可以帮助大家更好的理解。
一、vector文档入口
点击这里——>vector文档
①构造函数
default (1) explicit vector (const allocator_type& alloc = allocator_type());
fill (2)explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
range (3)template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
copy (4)vector (const vector& x);
int main()
{
//(1)创建空vector
vector<int> v;
//(2)填充构造函数
vector<int> v(5,3);//每个都是3,即[3,3,3,3,3]
//(3)范围构造函数
int arr[] = {1,2,3,4};
vector<int> v(arr+1,arr+4);//用arr的第二个元素开始到最后一个元素进行初始化
//(4)拷贝构造函数
vector<int> v1 = {1,2,3};
vector<int> v2(v1);
}
②插入数据相关接口(push_back,insert)
void push_back (const value_type& val);
single element (1)iterator insert (iterator position, const value_type& val);
fill (2)void insert (iterator position, size_type n, const value_type& val);
range (3) template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
int main()
{
//push_back,只能尾插一个元素
vector<int> v = {1,2,3};
v.push_back(4);
//(1)single element,插入单个元素
vector<int> v = {1,2,3};
auto it = v.begin()+1;//it为指向第二个元素的迭代器
v.insert(it,5);//最终变成了{1,5,2,3};
//(2)fill,插入多个相同的元素
vector<int> v = {1,4};
auto it = v.begin();
v.insert(it,2,3)//插入两个3,v变为{3,3,1,4}
//(3)range,迭代器范围插入
vector<int> v = {1,5};
int arr[] = {2,3,4};
auto it = v.begin()+1;//it指向第二个元素
v.insert(it,arr,arr+3);//在第二个元素的位置插入了234,最终变为{1,2,3,4,5}
}
③删除相关接口(pop_back,erase)
void pop_back();
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
int main()
{
//push_back
vector<int> v = {1,2,3};
v.push_back();//v为{1,2}
//erase单个元素
vector<int> v = {1,2,3};
auto it = v.begin();//it为指向首元素的迭代器
v.erase(it)//删除首个元素,v为{2,3}
//erase删除迭代器区间的范围元素(左闭右开)
vector<int> v = {1,2,3,4,5};
auto first = begin(),last = begin+4;//first指向首元素,last指向最后一个元素
v.insert(first,last);//遵循左闭右开的原则,最后v为{5}
}
④容量相关的容器(size,resize,capacity,empty,reserve)
1.size
size_type size() const;
2.resize
void resize (size_type n, value_type val = value_type());
3.capacity
size_type capacity() const;
4.empty
bool empty() const;
5.reserve
void reserve (size_type n);
二、vector的底层结构
vector的底层就是由数据实现的,只不过多了三个指针分别指向首元素(_start)、最后一个元素的下一个位置(——finish)以及指向容量大小的下一个位置(_endof_storeage)。
如图:

初始框架
那就让我们先来定义一下vector的初始框架,再一步一步补上相关的容器。
template<class T>
class vector
{
public:
size_t size()const
{
return _finish - _start;//指针相减得到得到他们的相对位置,这里得到的是目前元素个数
}
size_t capacity()const
{
return _endof_stareage - _start;//这里得到目前容量大小
}
private:
T* _start;//指向首元素的指针
T* _finish;//指向最后一个元素的下一个位置的指针
T* _endof_storeage;//指向容量的下一个位置的指针
};
构造相关逻辑
//拷贝构造
public:
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endof_storeage(nullptr)
{
for(auto e& :v)
{
push_back(e);
}
}
//迭代器区间构造
template<class InputIterator>
vector(InputIterator first,InputIterator last)
{
while(first != last)
{
push_back(*first);
}
}
//Initialzar_list构造
vector(initialzar_list<T>& v)
{
for(auto e& : v)
{
push_back(e);
}
}
扩容以及插入操作
这里需要注意我们需要扩容的条件是什么,当_finsih == _endof_storeage时,就需要进行扩容
class vector
{
public:
void reserve(n)
{
size_t old_size = size();
if(n > capacity())
{
T* tmp = new T[n];
if(_start)//如果_start不为空,也就是非初始化的时候
{
memcpy(tmp,_start,sizeof(T)*size());//memcpy是按字节进行拷贝,将原先的数据拷贝到新创建的数组空间中。
delete[] _start;//释放旧空间的内容
}
_start = tmp;
_finish = _start + old_size;//不可写为_statr+size(),size()是_finsih - _start,_start更新后指向了新空间,而_finsih还指向着旧空间,无相减的意义。
_endof_storeage = _start + n;
}
}
void push_back(const T& x)
{
if(_finish == _endof_storeage)
{
reserve(capacity() == 0? 4 : 2 * capacity());
}
//走到这里。扩容完毕
(*_finish) = x;
_finish++;
}
};

insert与erase
这两者都是相当重要的接口,因为他们的操作会涉及到一个非常重要的知识点——迭代器失效
什么是迭代器失效?
举个直观的例子:你手里拿着“指向容器第3个元素”的迭代器,结果容器因为插入/删除元素重新分配了内存、或者元素顺序被打乱,这时候你再用这个旧迭代器去访问元素,要么拿到错误的数据,要么直接程序崩溃——这就是“迭代器失效”的本质:迭代器原本指向的“位置/状态”,因为容器的修改变得无效了。
那么,迭代器失效有什么影响?
使用失效的迭代器访问容器元素时,会触发内存访问违规,直接导致程序崩溃,所以迭代器失效是绝对不可取的。
解决方法:
insert与erase操作后,为避免迭代器失效,他俩会更新迭代器。例如insert,会返回指向被插入元素的迭代器(例如我们想插入5,返回的是指向5的新迭代器);erase则会返回迭代器指向原本元素的下一个元素(12345我们想删去3,则返回的是1245指向4的迭代器)
class vector
{
public:
iterator insert(iterator pos,const T&x)
{
if(_finish == _endof_storeage)
{
size_t len = pos - _start;
reserve(capacity == 0?4:2*capacity());
pos = _start + len;//这里进行了扩容操作,_start已经进行了更新,如果我们不这么做,pos还是指向原来的旧空间。
}
iterator end = _finish;
while(end >= pos)
{
*end = *(end - 1);
end--;
}
*pos = x;//迭代器失效:指向的内存废了,这里只是合法中的内存数据被修改
++_finish;
return pos;
}
iterator erase(iterator pos)
{
iterator end = pos;
while(end+1<_finish)
{
*end = *(end+1);
end++;
}
--_finish;
return pos;
}
};
总结
本文主要针对vector作了一个大致的讲解,对一些重要的接口进行了细致的解答,还需大家下功夫去练习与理解。

4030





