文章目录
- vector的介绍
- vector的使用
- vector的初始化
- vector iterator迭代器的使用
- vector 空间增长问题
- vector的增删改查
- 迭代器失效
- 总结
vector的介绍
文档介绍
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
vector的使用
下面是对于vector的基本用法的讲解
vector的初始化
(constructor)构造函数声明 | 接口说明 |
---|---|
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
主要使用的是无参构造和拷贝构造,其他两种不常用,下面代码一并介绍
void test1()
{
//1.vector()(重点) | 无参构造
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for (auto c : v1)
{
cout << c << " ";
}
cout << endl;
//2.vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val
vector<int> v2(10, 0);
for (auto c : v2)
{
cout << c << " ";
}
cout << endl;
//3.vector(const vector & x); (重点) | 拷贝构造
vector<int> v3(v1);
for (auto c : v3)
{
cout << c << " ";
}
cout << endl;
//4.vector(InputIterator first, InputIterator last);
vector<int> v4(v2.begin(), v2.end());
for (auto c : v4)
{
cout << c << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
vector iterator迭代器的使用
vector的迭代器有两种,正向和反向迭代器
正向:begin() 和 end() 分别表示获取第一个数据的位置,获取最后一个数据的下一个位置
反向:rbegin() 和 rend() 分别表示获取最后一个数据的位置,获取第一个元素的前一个位置
//迭代器的名称分别为 iterator 和reverse_iterator
void test2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//auto rit=v.rbegin();
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
}
int main()
{
test2();
return 0;
}
vector 空间增长问题
使用几种函数来管理和查看vector的空间
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
主要是对于resize和reserve的使用,empty就是判别是否为空,size和capacity是查看当前vector的数据个数和容量
void test3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(20);
cout << "更改size为20" << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(40);
cout << "更改capacity为40" << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(50);
cout << "更改size为50" << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
}
void test4()
{
vector<int> v;
size_t ans = v.capacity();
for (int i = 0; i < 200; i++)
{
v.push_back(i);
if (ans != v.capacity())
{
cout << "capacity为:" << ans << endl ;
}
ans = v.capacity();
}
}
int main()
{
test4();
return 0;
}
注意:capacity的增长倍数是由环境决定的,vs下在1.5倍数左右,在Linux下近乎2倍,且关于resize和reserve,我们只需要知道resize底层是调用reserve的,满足size<=capacity
vector的增删改查
vector的增删改查 | 接口说明 |
---|---|
push_back | 尾插 |
pop_back | 尾删 |
find | 查找指定的数据,返回下标(在算法模块,不在vector的成员函数中) |
insert | 在pos位置上插入指定数值val |
erase | 删除指定pos位置上的数据 |
swap | 交换两个vector对象的数据空间 |
operator[ ] | 和数组一样,通过下标方括号来访问vector的数据 |
STL分为两大模块,容器和算法,容器例如vector、list、set等,算法是在头文件<algorithm>中,是将容器中通用的函数归档在<algorithm>中,按照迭代器的不同,不同容器能使用相同或不同的函数。
push_back 和 pop_back 以及operator[ ]的使用
简单理解,不必做详解
void test5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//push_back的使用,传参val const value_type& val value_type实际就是T 模板类
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//pop_back 的使用,不必传参,尾删
v.pop_back();
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//operator[]的使用 可以访问指定下标位置元素,也可赋值
cout << v[2] << endl;
v[2] = 10;
cout << v[2] << endl;
}
int main()
{
test5();
return 0;
}
find
在<algorithm>算法中,InputIterator find (InputIterator first, InputIterator last, const T& val);
#include<algorithm>
void test6()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it =find(v.begin(), v.end(), 5);
if (it != v.end())
{
cout << *it << endl;
}
else if(it == v.end())
{
cout << "没有找到val,返回的是v.end()" << endl;
}
}
int main()
{
test6();
return 0;
}
insert
insert的插入,实际上就是先得到要插入的数值个数n,然后将vector中的元素后移n位,然后依次填入插入的数据
void test7()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//1.iterator insert (iterator position, const value_type& val);
auto it=v.insert(v.begin() + 1, 10);
cout << "返回迭代器指向的数值为:" << *it << endl;
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//2.void insert (iterator position, size_type n, const value_type& val);
v.insert(v.begin(), 5, 6);
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//3.void insert(iterator position, InputIterator first, InputIterator last);
v.insert(v.begin(), v.begin(), v.end());
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
vector<int> v1; //后两个参数,只要是迭代器即可,可以是其他容器的迭代器数据插入过来
v1.insert(v1.begin(), v.begin(), v.begin()+6);
for (auto c : v1)
{
cout << c << " ";
}
cout << endl;
}
int main()
{
test7();
return 0;
}
erase
void test8()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//指定pos迭代器删除
auto it =v.erase(v.begin()); //返回的是删除之后的v.begin()
cout << *it << endl;
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
//区间删除
it=v.erase(v.begin() + 1, v.begin()+2);
cout << *it << endl; //返回的是删除之后的v.begin() + 1 但是要注意越界问题
for (auto c : v)
{
cout << c << " ";
}
cout << endl;
}
int main()
{
test8();
return 0;
}
迭代器失效
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,所以迭代器失效实际上就是迭代器底层对应指针所指向的空间被销毁了,而使用一窥啊已经倍释放的空间,当继续使用已经失效的迭代器,会造成程序崩溃。
迭代器失效的情景
- 引起底层空间的改变都有可能是造成迭代器失效,如resize、reserve、insert、push_back等。他们的同意特性就是会在底层调用reserve来判断增加元素时,是否需要扩容。
- 指定位置元素的删除操作,erase(pos)
第一种场景,统一导致迭代器失效的是,由于增加元素或者手动扩容,导致vector扩容,然而扩容机制,底层是新建一个T指针数组,拷贝原有数据到新数组中,然后释放原来的数组空间,所以迭代器会失效(此时的迭代器指向的是一块被释放的空间),导致程序崩溃
void test9()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//底层的改变实际上就是是否扩容了
//先获得当前vector的首元素的迭代器
vector<int>::iterator it = v.begin();
cout << *it << endl;
//1.如果经历了resize ,触发扩容,迭代器失效 代码为 -1073741819。
//v.resize(50, 0);
//cout << *it << endl;
//2.如果使用reserve手动扩容的话,也会导致迭代器失效
//v.reserve(100);
//cout << *it << endl; //代码为 -1073741819。
//3.如果是插入元素,导致扩容,也会导致迭代器失效
//v.insert(v.begin(), 100, 1);
//cout << *it << endl; //代码为 -1073741819。
//4.如果是push_back 导致扩容,也会发生迭代器失效
for (int i = 0; i < 100; i++)
{
v.push_back(i);
}
cout << *it << endl; //代码为 -1073741819。
}
int main()
{
test9();
return 0;
}
第二种场景,erase函数,删除指定pos位置数据,或者删除迭代器区间的数据。我们先用auto it=v.begin()+5,获得迭代器,然后我们删除一个元素,删除之后有两种可能:1.it指向的位置还有元素,那么按照道理来说 cout一下还是能正常输出数值 2.it位置已经没有元素了,此时it指向的空间已经是vector.end()之后了,再次访问it的时候,就会报错,相当于越界了。
综合上述两种可能,因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
void test10()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
v.erase(v.begin());
//v.erase(v.begin(), v.end());
cout << *it << endl; //代码为 -1073741819
}
int main()
{
test10();
return 0;
}
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
总结
vector的优点:
1.支持随机访问(以[]的方式)
2.cpu高速缓存效率高
vector的缺点:
1.中间和头部删除元素效率低(要移动数组元素)
2.扩容问题(扩容会导致迭代器失效)
总结:
- vector的使用实际上大部分的函数与string中的用法相似,我们只需要知道一点的是,vector是一个可变长的连续有序序列即可,不管是空间还是物理上,数据是连续的。
- vector的初始的capacity的大小和初始化的数据个数有关,然后依次为基点,在VS平台以近似1.5倍扩容,在Linux下以2倍扩容。
- 在vector中我们初步认识到了<algorithm>的存在,这是算法,包含在STL中,STL分为容器和算法,<algorithm>中是总结所有容器所需要的接口,通过迭代器来实现。不同的容器有不同的迭代器,能实现不同的算法接口。
- 迭代器失效问题,是string以及vector在涉及到扩容或者erase中会发生的问题,但是erase返回参数为iterator,所以只需要再次赋值即可,而扩容导致的迭代器失效,我们只能再次对迭代器重新赋值,取得新的迭代器。随用随取,在使用前,对迭代器重新赋值。
- 综上,我们可以通过vector,不必顾忌数组的长度问题,通过相应的便捷的接口,来实现更多可能性的操作。