vector(可增容的数组,顺序表)与迭代器失效又擦出火花?

三种方式的遍历

  1. 下标+[ ]遍历
vector<int> V1(10,0);      //初始化为10个0
for(size_t i=0;i<V1.size();++i)
{
	cout<<V1[i]<<" ";
}
cout<<endl;
  1. 迭代器
vector<int> V1(10,1);
vector<int>::iterator it1=V1.begin();
//V1.begin()返回的是首元素的指针;
while(it1!=V1.end())  //V1.end()返回的是最后一个元素的下一个位置的指针
{
	cout<<*it1<<" ";
	++it1;
}
cout<<endl;
  1. 范围for
//会自动判断结束,自动迭代++
for(auto e: V1)
{
	cout<<e<<" ";
}
//虽然表面看起来很智能,其实就是被替换成了迭代器而已

vector遍历时,我们喜欢用下标+[ ]的方式,好用简单;
但是得掌握迭代器,因为迭代器才是所有容器的通用访问方式,任何容器都可以用迭代器访问且方法非常相似。

想和string类的三种遍历进行对比复习,可以点这里哦!

构造初始化方式

vector<int> v1;
//无参构造
vector<int> v2(10,0);
//构造初始化为10个0
vector<int> v3(v2.begin(),v2.end());
//支持不同类型的迭代器
string s("hello");
vector<char> v4(s.begin(),s.end());
/*看起来好像在此处多此一举,
但要注意的是vector不能去代替string,
一方面string和vector的接口函数还是有差异的,
另一方面string是有“\0”结尾的,vector没有*/
vector<string> v5;
v5.push_back("RenH");
//单参数的构造函数发生隐式类型转换
//与之前string s1="RenH";可以这样构造的原因相同

打印vector

//为了方便打印不同类型的vector,所以我们使用模板
template <class T>
void PrintVector(const vector<T>& v)
{
//自己熟悉的情况下,可以使用auto it1=v.begin()替换
	vector<T>::const_iterator it1=v.begin();
//传参是const+引用,所以迭代器需使用const_iterator
	while(it1!=v.end())  
	{
		cout<<*it1<<" ";
		++it1;
	}
	cout<<endl;
}

拷贝构造

vector<string> copy(v5);
//自己熟悉的情况下,可以使用auto rit=copy.rbegin()替换
vector<string>::reverse_iterator rit=copy.rbegin();
//copy.rbegin()返回最后一个元素的指针
while(rit!=copy.end())  //copy.rend()返回首元素的前一个位置的指针
{
	cout<<*rit<<" ";
	++rit;
}
cout<<endl;
/*上述中的遍历使用的是反向迭代器,
注意与前面遍历中的正向迭代器进行区分*/

数据插入和删除

vector<int> v1;
//使用push_back尾插
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
//使用pop_back尾删
v1.pop_back();
//使用insert头插,第一个参数为迭代器位置
v1.insert(v1.begin(),6);
//使用erase删除,两个参数均为迭代器位置
v1.erase(v1.begin());
//第二个参数缺省,此处默认为v1.begin()+1,所以为头删第一个

std::find查找

vector<int>::iterator pos=find(v.begin(),v.end(),3)
if(pos!=v.end())
{
	v.insert(pos,30);
}
for(auto e: v)
{
	cout<<e<<" ";
}
cout<<endl;
v.erase(pos);
//会发现没删除掉3,反而把30删了
//此处发生了迭代器失效

迭代器失效

insert和erase都会导致迭代器失效,分为两种情况:
1.insert(it,x)或者erase(it)以后迭代器的意义变了
2.insert(it,x)或者erase(it)以后,it变成了野指针(增容/缩容)

上述代码中pos失效的两种可能 :1、在上述样例中,pos的意义变了,插入数据以后,pos不再指向3,而是指向30,从而导致earse(pos)没有达到删除3的目的,反而把30删除了。
2、可能还会崩溃,出现野指针,如下面例子中,这里迭代器失效不仅仅是意义变了,而是因为insert出现增容后,pos位置的指向空间被释放了,pos已经是野指针了。
错误示例1野指针的同时意义也变了):

在这里插入图片描述
具体代码如下:

vector<int> v1;
//使用push_back尾插,至v1的size和capacity一样大
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(6);
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	if (pos != v1.end())
	{
		v1.insert(pos,30);
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	v1.erase(pos);  //此处为迭代器失效

解决方案如下:

//前面的代码不变
//因为pos在insert之后就失效了,那就不要用原来的他
pos = find(v1.begin(), v1.end(), 3);
v1.erase(pos);

错误示例2(意义变了,也可能会出现越界):

	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(6);
	v1.push_back(7);
//假设我们要求删除v中的所有偶数
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			v1.erase(it);
		}
		++it;
	}

此时在不同环境下的结果可能不同:
VS2013下:
在这里插入图片描述

g++下由于巧合可能会出现正确结果:
在这里插入图片描述但稍微改动一下就可以发现,明显的错误:

将v1.push_back(3)改为push_back(30);

结果如下:

在这里插入图片描述
原因是由于在it进行遍历检查时,每遇到一个偶数删除后,便会跳过下一个元素的检查!!
在这里插入图片描述
解决方案:

	//前面的代码不变
	//要求删除v中的所有偶数
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			//erase(it)后迭代器失效,it不能++
			//erase会返回删除元素的it的下一个位置,即正确的迭代器位置
			v1.erase(it);
		}
		else
		{
			++it;
		}
	}

拓展小知识:
友友们仔细观察上面的迭代器实现使用过程,有没有发现在迭代器的比较中都使用的是“!=”或者“==”,而不用“>”、“<”呢?
其实是因为面对string、vector这样底层是连续内存的容器都可以使用,但是面对list、map等链式结构就只能用!=或 ==了


今天的内容就到这里了哈!!!
要是认为作者有一点帮助你的话!
就来一个点赞加关注吧!!!当然订阅是更是求之不得!
最后的最后谢谢大家的观看!!!
你们的支持是作者写作的最大动力!!!
下期见哈!!!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值