【STL学习之路】vector的使用 | vector的迭代器失效问题

vector介绍

image-20221226175353302

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获取最后一个位置迭代器 / 获取第一个位置迭代器

image-20221226182419304

//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[]像数组一样访问
findSTL算法(头文件: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为例)

image-20221226203854865

push_back的参数是 const value_type& val

关键:1. 参数是引用 2. const。 为什么要这样设计?

  1. 使用引用:传递自定义类型的时候,自定义类型可能很大,所以用引用可以减少拷贝
  2. 使用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算法:

image-20221226201812920

// 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;
}

该程序存在的问题:
在这里插入图片描述
解决方法:

  1. insert设置一个返回值,根据STL规范,返回新插入的元素的迭代器
  2. 遍历的时候,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中其他的容器之后博主还会继续总结~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2021狮子歌歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值