vector的相关介绍
- vector是表示可变大小数组的序列容器。我们学习的时候就可以把它看作是像数组一样的东西。
- vector采用的连续存储空间来存储元素。也就是意味着我们可以采用下标对vector的元素进行访问,时间复杂度和数组一样高效,为O(1)。但是又不完全像数组,因为它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- vector使用动态分配数组来存储它的元素,当新元素插入时,为了增加存储空间,这个数组需要被重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。
- 与其它动态序列容器相比(deque, list and forward_list),vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。
想要了解更多关于vector的详细内容,请点击vector的文档介绍
vector的使用
注意:本章只介绍一些比较常用的接口
vector的相关定义
构造函数声明 | 接口说明 | 代码演示 |
vector() | 无参构造 | vector<int> v1; |
vector(size_t n, const T& val = T()) | 构造并初始化n个val | vector<int> v2(5, 10); //初始化为5个值为10的整数 |
vector(const vector<T>& v) | 拷贝构造 | vector<int> v3(v2); |
vector(InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 | vector<int> v4(v3.begin(), v3.end()); |
vector的空间增长问题
容量空间 | 接口说明 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
特别说明:
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
vector的增删查改
vector的增删查改 | 接口说明 | 代码演示 |
insert(慎用) | 在pos位置之前插入val | iterator insert(iterator pos, const T& val) |
erase(慎用) | 删除pos位置数据 | iterator erase(iterator pos) |
operator[] | 像数组一样访问 | T& operator[] (size_t n) |
ps. 这些内容我会在下面讲解底层实现的代码,请耐心阅读
vector的迭代器失效问题
迭代器是什么?简单理解迭代器就是像指针一样的东西,我们在使用的时候不用关心其底层的细节,只需像指针一样来使用就可以。而迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,使用一块已经被释放(销毁)的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
底层空间扩容时,可能会引起迭代器失效
比如:resize、reserve、insert、assign、push_back等,都会引起迭代器失效
例:使用insert往某个位置插入元素时,可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之前的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。解决方法:给it重新赋值即可。
int main()
{
vector<int> v1{1,2,3,4};
// find函数:查找某个元素,如果找到了则返回该元素的迭代器,否则返回end()位置的迭代器
vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
if (it != v1.end())
{
// 在查找的元素前插入200
// v1.insert(it, 200); 这样写则会导致迭代器失效
it = v1.insert(it, 200); //给it重新赋值
cout << *it << " ";
}
cout << endl;
return 0;
}
指定位置元素的删除操作,可能会引起迭代器失效
例:erase的功能是删除pos位置的元素。erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
int main()
{
vector<int> v1{1,2,3,4};
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
//给it重新赋值,解决迭代器失效的问题
it = v1.erase(it);
else
++it;
}
return 0;
}
注意:无论上面说的那种迭代器失效,都是针对vs所说;对于g++结果是不相同的
vector部分接口函数的底层实现
vector的初始化函数
//使用迭代器进行初始化构造
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//n个val构造
vector(size_t n, const T& val = T())
{
reserve(n); //提前开好空间,减小消耗
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());
for (const auto& e : v)
{
push_back(e);
}
}
vector的插入、删除函数
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start; //记录pos的位置
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
pos = _start + len; //更新新空间pos的位置 (迭代器失效的根本原因)
}
iterator end = _finish;
while (end >= pos) //从后往前依次挪动元素
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos;
while (begin < _finish - 1) //从前往后挪动元素
{
*(begin) = *(begin + 1);
++begin;
}
--_finish;
return pos;
}