文章目录
vector介绍
vector就是顺序表的封装,是一个模板类,如上图所示。为方便vector里可以存任何数据类型,因此搞成了模板。
第一个参数是模板类型T,T可以是int,double,char等,也可以是其他class或者struct
第二个参数是一个空间配置器
,(STL极致追求效率,向内存池来申请空间而不是直接向堆)。这个参数是缺省,一般不用管它。
vector使用
一、构造函数
构造函数 | 接口说明 |
---|---|
vector() | 无参构造 |
vector(size_type n,const value_type& val = value_type() | 构造并初始化n个val |
vector(const vector& x) | 拷贝构造 |
vector(InputIterator first,InputIterator last) | 使用迭代器构造 |
1. 全缺省参数
vector<int> vi;
vector<double> vd; //调用全缺省构造
2. n个val构造
vector<int> vi(5,0); //用5个0构造
char ch = 'x';
vector<char> vc(100,ch); //用100个ch构造
3. 拷贝构造
vector<int> v1(5,10);
vector<int> v2(v1); //用已存在的v1构造
vector<int> v3(vector<int>(10,6)); //匿名对象构造
4. 迭代器区间构造
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(arr)/sizeof(int);
vector<int> v(arr,arr+len); //arr的[0,len)构造
二、vector的空间增长问题
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
size 和 capacity
#include<iostream>
using namespace std;
int main()
{
vector<int> v(10,5);
cout << v.size() << endl;//获取vector中有效数据个数
cout << v.capacity() << endl;//获取当前容器的最大容量
return 0;
}
empty
判断当前容器是否为空(返回值:bool类型)
#include<iostream>
using namespace std;
int main()
{
vector<int> v(10,5);
if(v.empty())
cout <<"vector为空"<<endl;
else
cout <<"vector不为空"<<endl;
return 0;
}
三、迭代器
Iterator的使用 | 接口说明 |
---|---|
begin + end | 获取第一个数据位置的迭代器 / 获取最后一个位置迭代器 |
rbegin + rend | 获取最后一个位置迭代器 / 获取第一个位置迭代器 |
//1.普通迭代器
int arr[] = {1,2,3,4,5,6,7,8,9};
vector<int> v(arr,arr+sizeof(arr)/sizeof(int));
vector<int>::iterator it = v.begin(); //普通迭代器
vector<int>::cont_iterator c_it = v.begin(); //const迭代器
vector<int>::reverse_iterator r_it = v.rbegin();//reverse迭代器(反向)
vector<int>::const_reverse_iterator c_r_it = v.rbegin();//反向const迭代器
//使用方法都相同,以普通迭代器为例
while(it != v.end() )
{
cout << *it << " ";
++it;
}
//注意 如果是反向迭代器必须使用rbegin和rend
while(r_it != v.rend()){ /* ... */ }
resize和reserve
resize
1.当所给的值大于当前容器的size的时候,讲size扩大到该值,resize第二个参数不给的时候,默认用0扩,如果给了就用所给的那个值扩容
2.所给的值小于当前容器的size,就直接将size缩小到该值(起到删除的作用)
reserve
1.所给值小于等于当前容器的capacity,什么也不做
2.所给值大于当前容器的capacity,将capacity扩大到所给值
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.reserve(30); //capacity->30
v1.reserve(6); //capacity不变
v1.resize(6, 2); //size变成6,第6个用2扩充
v1.resize(1); //size变成1,只剩下1 这个元素
return 0;
}
四、vector增删查改
vector增删查改 | 接口说明 |
---|---|
push_back | 尾插 |
pop_back | 尾删 |
insert | 再position前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] | 像数组一样访问 |
find | STL算法(头文件:algorithm) |
find函数
参数:前两个表示一个迭代器区间,左闭右开:[first,last)
第三个参数就是要查找的value
返回值:如果找到了,返回目标位置的迭代器;如果找不到,返回所给的第二个参数,即last
push_back,pop_back 和 遍历vector
vector<int> v;
//插入元素
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
v.push_back(7); //v: 1 2 3 4 5 6 7
//删除元素
v.pop_back();
v.pop_back(); // v: 1 2 3 4 5
// ↓ 三种遍历方式
//遍历1:利用opertor[],可以像数组一样去访问元素
for(size_t i = 0;i<v1.size();++i)
{
cout << v[i] <<" ";
}
cout << endl;
//遍历2:迭代器
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//遍历3:范围for
//利用范围for遍历
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
insert和erase
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
//找到3的位置,在3的位置插入30 -- find使用迭代器
#include<algorithm>
vector<int>::iterator position = find(v1.begin(),v1.end(),3);
//如果找不到返回end()
if (position != v1.end())
{
v1.insert(position,30);
}
//如果找不到就不用动
for (auto i : v1)
{
cout << i << " ";
}
cout << endl;
/* 删除 */
//找到4并删除
position = find(v1.begin(), v1.end(), 4);
//如果找到了就删除
if (position != v1.end())
{
v1.erase(position);
}
for (auto i : v1)
{
cout << i << " ";
}
cout << endl;
五、关于STL中参数的分析(以push_back为例)
push_back的参数是 const value_type& val
关键:1. 参数是引用 2. const。 为什么要这样设计?
- 使用引用:传递自定义类型的时候,自定义类型可能很大,所以用引用可以减少拷贝
- 使用const:传递匿名对象,或者直接传递参数返回值,必须要用const接收
//以string为例 那么参数类型对于的是 const string&
vector<string> v;
//1.最初是这样用:
string str = "hello"
v.push_back(str);
//2.上面太麻烦,改用匿名对象
v.push_back(string("hello")); //匿名对象具有常属性,const是必须
//3.隐式类型转换
v.push_back("hello");
六、sort算法和仿函数使用
sort算法:
// sort第三个参数是一个仿函数,其实是一个类模板
// 缺省值是less类型 -> 升序
// 降序:greater类型
// less和greater都在 头文件中
// 但是如果只包含algorightm头文件,只能用less 因为包含了算法 就包含了排序 就可以用(排序默认是less) 但是没有包含greater类型
#include<vector>
#include<algorithm>
#include<functional>
vector<int> v1;
v1.push_back(3);
v1.push_back(5);
v1.push_back(1);
v1.push_back(4);
v1.push_back(2);
v1.push_back(6);
sort(v1.begin(), v1.end());
for (auto i : v1)
{
cout << i << " ";
}
cout << endl;
//打印 1 2 3 4 5 6
//定义仿函数对象
less<int> ls;
greater<int> gt;
sort(v1.begin(), v1.end(), gt); //传递greater类型的仿函数-->降序
for (auto i : v1)
{
cout << i << " ";
}
cout << endl;
//输出: 6 5 4 3 2 1
vector的迭代器失效
什么是迭代器失效?
我们认为,迭代器失效就是指:迭代器不再指向原来的位置
有两种情况:
1、 因扩容后,迭代器没有及时更新导致迭代器变成野指针
2、 因数据挪动导致 对应位置的迭代器不再指向原来的值,比如原来指向5的迭代器,挪动完数据指向了6
举几个例子
insert导致的迭代器失效实例(一)
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto p = find(v.begin(),v.end(),3); //找到3
if(p!=v.end())
{
v.insert(p,30);//p迭代器已经使用完了
}
for(auto i : v)
{
cout << i <<" ";
}
cout << endl;
//对使用完了的位置p,没有更新就继续使用
cout << *p << endl;
}
程序运行结果:崩溃!
因为存在这样的问题:如果此时已经是满的,那么需要进行扩容,而扩容就会发生下面的这种情况:扩完容,pos迭代器已经被释放了,所以就变成了野指针,自然就会崩溃。
并且,插入该元素不会发生扩容,那么插入后pos的位置也会发生改变,因为要挪动数据,所以pos变成了指向30的迭代器。准确来说,pos后面的迭代器都不再指向原来的值了
而如果去监视窗口查看pos,就会发现pos是有意义的啊:
那么为什么继续使用pos会崩溃?实际上,针对上面的情况,只要你insert使用完pos迭代器,如果不进行更新而继续使用,VS就默认是崩溃了(为了安全不让你继续使用了)
疑惑:为什么不把insert的形参pos设置为引用呢,这样在内部insert的时候,如果发生扩容就把pos在扩容后的新空间更新一下,外部的也自然就更新了,就不用刻意避免再次使用pos了?
原因
是这样的,有时候我们会利用insert进行头插
insert(v.begin(),val)
而v.begin()返回第一个位置的迭代器的时候,函数返回值是会产生一个临时变量的,而临时变量具有常性,需要const引用接收。
因此是不可以滴!
总之:使用完insert之后,迭代器pos如果不更新,就不要使用啦!!
insert导致的迭代器失效实例(二)
当利用迭代器遍历的时候出现失效
如图:要求在所有的偶数前面插入该偶数的2倍
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.insert(it, *it * 2);
++it;
//无休止循环下去 因为只向后走了一格,本身就会向后挪动
//相当于没动
}
else
{
++it;
}
}
for (auto i : v)
{
cout << i << " ";
}
cout << endl;
return 0;
}
该程序存在的问题:
解决方法:
- insert设置一个返回值,根据STL规范,返回新插入的元素的迭代器
- 遍历的时候,it接收返回值,然后根据具体情况对it进行调整,本题是it++两次。如:第一次遇到偶数2,插入后返回4的迭代器,然后下一次应该从2的下一个位置开始寻找偶数,所以it接收返回值后,再++两次指向3。
另一个注意问题:对应std中的vector,VS是做了处理的:在it位置insert之后,it是不能继续使用的,如果继续使用就会崩溃(之所以这样设计也是考虑到迭代器失效的问题,直接强制不让你用了。这里在实例一中也有体现。)
如果想继续使用,必须让 it = insert(),即让 it接收返回值
实例二正确代码
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.insert(it, *it * 2); //接收返回值
++it;
++it;
}
else
{
++it;
}
}
for (auto i : v)
{
cout << i << " ";
}
cout << endl;
}
erase导致的迭代器失效实例(一)
如图:要求删除所有的偶数
错误代码
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())//错误写法:存在迭代器失效
{
if (*it % 2 == 0)
{
v.erase(it);
}
++it;
}
for (auto i : v)
{
cout << i << " ";
}
cout << endl;
return 0;
}
解决方法:
insert和erase之后 都接收一下返回值来更新迭代器!
注意:erase也可能会发生野指针的失效,(如果有一个版本实现的是size低于capacity的1/2就会进行缩容,也会存在野指针的失效
正确代码
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(4);
v.push_back(5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it); //如果是偶数,就删除it位置
}
else
{
++it; //如果不是偶数,就++
}
}
for (auto i : v)
{
cout << i << " ";
}
cout << endl;
return 0;
}
关于迭代器失效问题,这里只是举了几个例子,之后更新STL中其他的容器之后博主还会继续总结~