C++ stl中的vector的相关用法 迭代器失效问题

vector的介绍及使用

1、vector是用于表示可变大小数组的序列容器。
2、vector就像数组一样,采用的是连续的空间来存储元素,也意味着可以通过下标对vector的元素进行访问和修改。
3、普通数组的大小不可以动态改变,vector的大小可以动态改变
4、当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间。
5、vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的。
6、由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低

vector的定义

方式一:
构造一个特定类型的空容器

vector<int> v; //构造int类型的空容器

方式二:
构造一个有n个val元素的特定类型的容器

vector<int> v(5, 2); //构造含有5个2的int类型容器

方式三:
调用拷贝构造方法,拷贝构造特定类型的容器

vector<int> v1(v); // v1为通过拷贝构造int类型的v容器构成

方式四:
使用迭代器拷贝构造某一段内容

vector<int> v1(v.begin(), v.end()); //使用迭代器拷贝构造v容器的某一段内容

这种方式还可以用于拷贝其它容器中的某一段内容
例如:

string s("hello world");
vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

vector的空间相关问题

1.size和capacity
size能够获取当前容器中的有效元素个数,capacity能够获取当前容器的最大容量

例子:

int main()
{
	vector<int> v(5, 2);
	cout << v.size() << endl; //获取当前容器中的有效元素个数
	cout << v.capacity() << endl; //获取当前容器的最大容量
	return 0;
}

2.resize
能够改变容器中的有效元素个数

resize使用规则

1、当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。
2、当所给值小于容器当前的size时,将size缩小到该值。

例子:

int main()
{
	vector<int> v(10, 2);
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //10
	
	v.resize(15); //改变容器的size为15 未给出扩大的元素 则用0填充
	cout << v.size() << endl; //15
	return 0;
}

3.reserve
能够改变容器的最大容量

reserve使用规则:

1、当所给值大于容器当前的capacity时,将capacity扩大到该值。
2、当所给值小于容器当前的capacity时,什么也不做。

例子:

int main()
{
	vector<int> v(10, 2);
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //10
	v.reserve(20); //改变容器的capacity为20,size不变
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //20
	return 0;
}

4.empty
能够判断当前容器是否为空

int main()
{
	vector<int> v(10, 2);
	cout << v.empty() << endl; // 为false
	return 0;
}

vector的迭代器的使用

在这里插入图片描述
迭代器分为正向迭代器反向迭代器
在这里插入图片描述

1.begin和end
begin:容器中指向第一个元素的迭代器
end:容器中指向最后一个元素的后一个位置的迭代器

例子:

int main()
{
	vector<int> v(10, 2);
	
	//正向迭代器遍历容器
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl; // 2 2 2 2 2 2 2 2 2 2
	return 0;
}

2.rbegin和rend
rbegin:容器中指向最后一个元素的迭代器
rend:容器中指向第一个元素的前一个位置的迭代器

例子:

int main()
{
	vector<int> v(10, 2);
	//反向迭代器遍历容器
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	// 输出结果虽然相同 但是输出顺序是相反的
	cout << endl; // 2 2 2 2 2 2 2 2 2 2
	return 0;
}

vector的增删查改

1.push_back和pop_back
push_back:对容器进行尾部插入元素
pop_back:对容器进行尾部删除元素

例子:

int main()
{
	vector<int> v;
	v.push_back(1); //尾插元素1
	v.push_back(2); //尾插元素2
	v.push_back(3); //尾插元素3
	// 此时容器中的元素为 1 2 3

	v.pop_back(); //尾删元素3
	v.pop_back(); //尾删元素2
	v.pop_back(); //尾删元素1
	return 0;
}

注意: 除了尾插和尾删之外,还有头插push_front和头删pop_front,不过在vector中进行头插和头删的效率较低。

2.insert和erase
insert:能够在所给迭代器的位置插入一个或者多个元素
erase:能够删除所给迭代器的位置的元素,或者删除所给迭代器区间的元素(注意左闭右开)

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.insert(v.begin(), 0); //在容器开头插入0 
	
	v.insert(v.begin(), 3, 5); //在容器开头插入3个5

	v.erase(v.begin()); //删除容器中的第一个元素

	v.erase(v.begin(), v.begin() + 5); //删除在该迭代器区间内的元素(左闭右开)
	
	return 0;
}

3.find
上面的例子中是按照位置进行插入或者删除,如果配合find函数使用时,则可以根据某个特定的值的位置进行插入或删除。即先使用find找到该位置,再在该位置进行插入或者删除

find函数有3个参数,前两个参数确定一个寻找的范围,为迭代器的区间(左闭右开),第三个参数为需要寻找的
find函数会在所给迭代器区间寻找第一个匹配的元素,找到则返回它的迭代器,如果没找到,则返回所给的第二个参数

例子:

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器
	
	v.insert(pos, 5); //在3的位置插入5

	pos = find(v.begin(), v.end(), 2); //获取值为3的元素的迭代器
	
	v.erase(pos); //删除2

	return 0;
}

注意: find函数是在算法库中实现的,不是vector中的成员函数,使用时需要包含头文件 < algorithm >

4.swap
能够交换两个容器的数据空间,将两个容器进行交换

int main()
{
	vector<int> v1(5, 1);
	vector<int> v2(5, 2);

	v1.swap(v2); //交换v1,v2的数据空间

	return 0;
}

5.[ ]进行元素访问
vector当中重载了[ ]操作符,所以我们可以通过下标+[ ]的方式对容器中的元素进行访问和修改

例子:

int main()
{
	vector<int> v(5, 2);
	//使用“下标+[]”的方式遍历容器
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " "; // 0下标开始
	}
	cout << endl;
	return 0;
}

vector支持迭代器,既然有迭代器,那么我们就可以使用范围for对vector容器进行遍历

int main()
{
	vector<int> v(5, 2);
	//范围for
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

vector迭代器失效问题

迭代器的主要作用是让我们使用容器时不用关心其底层的数据结构,vector的迭代器在底层实际上是一个指针
迭代器失效就是指迭代器底层对应的指针所指向的空间被销毁,而指向一块已经被释放了的空间,继续使用失效的迭代器则可能使程序崩溃

示例1:

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: 1 2 3 4 5
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
	v.insert(pos, 10); //在值为2的元素的位置插入10
	//v: 1 10 2 3 4 5
	v.erase(pos); //删除元素2 ???error(迭代器失效)
	//v: 1 2 3 4 5
	return 0;
}

说明:

在代码中,我们希望通过find找到元素为2的迭代器,然后在该位置插入一个元素10,再将元素2进行删除。但是实际上我们获取的是指向2位置的指针,当我们在2的位置上插入10之后,那么从2开始的元素都向后移动一位,此时原本指向2的指针现在指向了10,所以我们原本希望删除2,实际删除的是10

示例2:

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			v.erase(it);
		}
		it++;
	}
	return 0;
}

说明:

在该代码中,我们希望删除的是容器中的全部偶数,我们已知容器中的元素有{1,2,3,4,5,6};代码中使用迭代器it遍历容器中的元素,遇到偶数就删除,看起来好像没问题,但其实最终结果是错误的,因为将元素删除之后,后面的元素都会往前挪动一位,例如我们遇到1,1不是偶数,所以指向下一位,遇到2时,此时将2删除,2后面的元素都往前挪动一位,3往前挪动一位,就代替了2的位置,此时it++,那么it就直接指向元素4了,即迭代器it没有对3这个元素进行判断,因为原本3这个位置是2的,而2判断并且删除之后it就直接++了,所以如果这个3是偶数时,那结果就出错了。除此之外,当我们删除元素6时,元素6之后的迭代器v.end()也要向前移动,那么v.end()就指向了刚被删除的元素6的位置,此时it++,那么it就与v.end()错开了,访问了不属于容器的内存空间,导致程序崩溃

迭代器失效解决方法:
注意使用迭代器时,每次使用之前,对迭代器进行重新赋值

示例1解决方法:

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: 1 2 3 4 5
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
	v.insert(pos, 10); //在值为2的元素的位置插入10
	//v: 1 10 2 3 4 5
	pos = find(v.begin(), v.end(), 2); //重新获取值为2的元素的迭代器
	v.erase(pos); //删除元素2
	//v: 1 10 3 4 5
	return 0;
}

示例1中,在值为2的位置插入10之后,重新获取值为2的元素的迭代器,那么此时就可以对元素2进行删除了

示例2解决方法:

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			it = v.erase(it); //删除后获取下一个元素的迭代器
		}
		else
		{
			it++; //是奇数则it++
		}
	}
	return 0;
}

示例2中,我们接收erase函数的返回值(erase函数返回删除元素的后一个元素的新位置),并且只有当前位置元素为奇数时才it++,对于偶数,则删除当前元素之后继续判断该位置的元素(因为该位置元素被更新,所以需要再次判断)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值