【C++】vector的使用 以及 迭代器失效问题

前言
经过前面string的学习,我们已经掌握了许多string的类函数,vector中许多类函数与string中的类函数使用起来相似,例如迭代器的使用在所有的容器中使用都一样,这里我们不再介绍,下面我们学习一些vector类的一些常用的函数。

1.vector的文档介绍

2. vector在C++中表示可变大小数组的序列容器,使用时需要包含头文件 < vector > ,就像数组一样,vector也采用的连续存储空间来存储元素。


一、 vector的构造函数

vector的构造函数主要有四个,下面我们来一 一演示

在这里插入图片描述

第一个是无参的默认构造,第二个是我们可以用n个val去初始化vector,第三个也就是vector的拷贝构造,第四个是使用迭代器进行初始化构造。

int main()
{
    
    
	vector<int> v1;
	v1.push_back(1); //push_back的作用就是插入一个元素
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int> v2(4); //用4个匿名对象进行初始化
	vector<int> v3(4, 9); //用4个9进行初始化
	vector<int> v4(++v1.begin(), --v1.end());//用迭代器进行进行初始化
	vector<int> v5(v3); //拷贝构造
	return 0;
}

观察监视:

在这里插入图片描述

二、容量相关的函数

在这里插入图片描述
我们先来看看前三个:

int main()
{
    
    
	vector<int> v1(10, 2);
	cout << "size:" << v1.size() << endl;
	cout << "capacity:" << v1.capacity() << endl;
	cout << "empty:" << v1.empty() << endl;
	return 0;
}

在这里插入图片描述

然后我们先来看reserve函数,reserve函数的作用就是开辟新的空间改变capacity,但是不会改变size

  • reserve函数中的 n小于原始的容量时,此函数什么也不做。
  • reserve函数中的n 大于原始的容量时,就会扩大容量,注意里面原有的数据不做改变。

我们来看下面的一段代码:

int main()
{
    
    
	vector<int> v1(10, 2);
	v1.reserve(5);
	v1.reserve(15);
	return 0;
}

观察监视窗口我们会发现,执行reserve(5)时函数什么都没有做,执行reserve(15)时函数才会进行扩容。

在这里插入图片描述

在这里插入图片描述

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

下面我们再来看看resize函数,resize函数不仅会改变capacity也会改变size

  • 如果resize的参数n小于原始的size,那么就会保留前n个,后面的数据将会被销毁,但是capacity不变。

  • 如果resise的参数n大于原始的size,就会用第二个参数进行初始化size后面的空间,直到size == capacity

  • 如果resise的参数n大于原始的capacity,就会进行扩容并初始化未使用的空间。

我们来看下面一段代码:

int main()
{
    
    
	vector<int> v1(10, 2);
	v1.resize(5);
	v1.resize(15,7);
	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

vector 空间增长问题在的讨论

关于vector空间增长机制我们可以用下面的代码来进行测试。

void TestVectorExpand()
{
    
    
	size_t sz;
	vector<int> v;
	//记录第一次的capacity的值
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
    
    
		//挨个插入100个元素
		v.push_back(i);
		//如果容量发生了变化,就重新更新sz的数据,并打印sz
		if (sz != v.capacity())
		{
    
    
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

我们先来看一看windows下的扩容机制:

在这里插入图片描述

我们会发现windows平台下是1.5倍扩容!

我们再来看看Linux平台下的空间增长:

在这里插入图片描述
我们发现是2倍扩容。

原因是这两个平台采用的STL版本不同,vs是PJ版本STL,g++是SGI版本STL。

三、vector 数据的访问

在这里插入图片描述

代码演示:

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	cout << v1[2] << endl;
	cout << v1.at(2) << endl;
	cout << v1.front() << endl;
	cout << v1.back() << endl;
	int* pi = v1.data();
	pi[3] = 5;
	cout << pi[3] << endl;
	return 0;
}

在这里插入图片描述

四、vector的增删查改

在这里插入图片描述

1、assign函数

在这里插入图片描述
第一个函数的用一个迭代器区间进行赋值,第二个函数是用n个元素来进行赋值。

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	vector<int>::iterator it1 = v1.begin();
	while (it1 != v1.end())
	{
    
    
		cout << *it1;
		++it1;
	}
	cout << endl;

	vector<int> v2(2, 7);
	vector<int>::iterator it2 = v2.begin();
	while (it2 != v2.end())
	{
    
    
		cout << *it2 ;
		++it2;
	}
	cout << endl;
	
	//普通赋值
	v2.assign(3, 2);
	for (auto& e : v2)
	{
    
    
		cout << e ;
	}
	cout << endl;
	
	//用迭代器区间赋值
	v2.assign(v1.begin(), v1.end());
	for (auto& e : v2)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

2、insert函数

在这里插入图片描述
第一个函数是用1个元素来进行插入pos位置之前,第二个函数是用n个元素插入pos位置之前

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	v1.pop_back();
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	v1.insert(++v1.begin(), 2, 7);
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

3、erase函数

在这里插入图片描述

第一个函数是删除pos迭代器位置。第二个是删除迭代器区间的所有元素

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;

	v1.erase(++v1.begin(), --v1.end());

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

4、swap函数

在这里插入图片描述

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;

	vector<int> v2(9, 7);
	v1.swap(v2);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

5、clear函数

在这里插入图片描述

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	cout << endl;
	v1.clear();
	cout << "size:" << v1.size() << endl;
	cout << "capacity :" << v1.capacity() << endl;
	return 0;
}

在这里插入图片描述

6、find函数

find函数并不是vector里面的成员函数,而是C++里面的一个算法库里面的函数,其作用就是帮我们寻找我们想要的数据。

在这里插入图片描述
函数参数:两个迭代器,确定一个一个区间,最后面的参数是我们想要查找的元素。

返回值:当找到该元素后,返回该位置的迭代器,如果找不到,返回last迭代器。

注意:所有的迭代器区间都是左闭右开的,所以返回last迭代器代表找不到

int main()
{
    
    
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	//删除元素 2
	vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
	v1.erase(it);
	for (auto& e : v1)
	{
    
    
		cout << e;
	}
	return 0;
}

在这里插入图片描述

五、vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装

迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果可能未定义的或者是程序崩溃

但是由于vsg++采用的STL版本分别是PJ版本和SGI版本,因此对于迭代器的失效问题,在两个版本中的表现也不太相同。

迭代器的失效主要有两种方式:

  • 由于删除元素而导致的迭代器失效。
  • 由于容量改变而导致的迭代器失效

我们先来看下面由于删除元素而导致的迭代器失效:

int main()
{
    
    
	vector<int> v1{
    
     1,2,3,4,5 };
	vector<int>::iterator it = v1.begin();
	//删除it指向的元素
	v1.erase(it);
	//再对it指向的元素进行自增
	(*it)++;
	for (auto& e : v1)
	{
    
    
		cout << e ;
	}
	cout << endl;
	return 0;
}

erase删除it位置元素后,it位置之后的元素会往前搬移,但是我们再对it进行访问已经不合理了,因为最初时我们是想让it指向元素 1,元素1消失了,我们也不应该再对it进行访问了。

上面的代码在vs下的PJ版本会直接报错,而在g++下的SGI版本会访问下一个元素2

  • vs下执行(*it)++时会直接报错:
    在这里插入图片描述

  • g++下执行(*it)++时会直接访问后面移动到该位置上的元素,从而导致了导致2变成了3

    在这里插入图片描述

看到这里你可能觉得这里的迭代器好像也可以看成不失效啊,g++下还能通过it直接访问元素也是合理的啊,但是当我们上面的代码删除的是最后一个元素5,g++下还执行(*it)++时就发生了越界行为!

因此对于这个it如果我们还想使用我们就要对迭代器进行重新赋值,这也是解决迭代器失效的通用的方法。

下面我们再来看另外一种由于容量改变而导致的迭代器失效:

int main()
{
    
    
	vector<int> v1{
    
     1,2,3,4 };
	//it是未扩容之前的迭代器
	vector<int>::iterator it = v1.begin();

	v1.resize(50,0);
	//v1.begin()是扩容后的迭代器
	while (it != v1.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
  • vs下执行时会直接报错:
    在这里插入图片描述

  • g++下执行时也会执行错误:

    在这里插入图片描述
    发生了许多越界访问。

这里迭代器失效的原因:是我们对v1进行了扩容,而it是指向没有扩容之前的开始位置的迭代器,扩容以后内存地址发生了变化,而it没有及时更新。我们还使用以前的迭代器就有可能造成许多越界行为。

在这里插入图片描述

对于第二种迭代器失效,只要会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
因此同时使用可能引起其底层空间改变的函数和迭代器时要注意迭代器的更新!!

猜你喜欢

转载自blog.csdn.net/qq_65207641/article/details/129818586