C++STL详解(三)——vector类的接口详解

目录

一.vector的介绍

二.vector的构造以及赋值

2.1构造函数

 2.2operator=重载

三.vector的空间操作

3.1capacity和size函数

3.2reserve和resize函数

3.3empty函数

四.vector迭代器相关函数

4.1begin和end函数

 4.2rbegin和rend函数

五.vector的增删查改

5.1push_back和pop_back函数

5.2insert函数

 5.3erase函数

5.4find函数

5.5at和operator[]

六.vector的迭代器失效问题

6.1迭代器失效问题的举例

举例1

举例2

6.2迭代器失效问题的解决方法

6.3vs迭代器简析 

七.后记


一.vector的介绍

vector容器的底层是顺序表,在CPP中,我们对顺序表进行了一定程度的封装。

如果你不了解顺序表,那么可以浏览此片博文:顺序表

二.vector的构造以及赋值

2.1构造函数

在这里我们需要学习以下四个构造函数:

	vector();//空构造
	vector(size_type n, const value_type& val = value_type());//使用n个value构造
	vector(const vector & x);//拷贝构造
	vector(InputIterator first, InputIterator last);//迭代器区间构造(前闭后开)

构造1:构造一个空的vector容器

vector<int> v1;//空构造

构造2:构造含有n个val的vector容器

vector<int> v2(10,1);//十个1

 大家可能注意到了这个构造函数中的第二个类型是容器内的数值类型。而它给的缺省值是value_type();因此我们大家可能产生这样的疑惑:内置类型是如何处理的?

构造3:复制别的同类型容器构造

vector<int> v3(v2);//拷贝v2构造v3

构造4: 使用迭代器区间进行构造

vector<int> v4(v2.begin()+1,v2.end()-1);//使用迭代器构造

 2.2operator=重载

同样的,我们也可以使用重载后的operator=操作符来进行给一个空的vector容器赋值。

	vector<int> v6=v2;//将v2的值赋给v6

三.vector的空间操作

3.1capacity和size函数

我们在初始化一个vector容器之后,可以通过这两个函数查看该容器的空间大小和有效数据个数。

函数原型如下:

void capacity();
void size();

在我们用两个1初始化一个vector容器之后,可以使用这两个函数查看它的空间大小。 

3.2reserve和resize函数

和string相同的是,reserve函数的功能是设置空间大小,resize函数的作用是设置有效数据个数。

reserve:

  • 如果设置的空间大于当前capacity,则扩大空间至capacity或大于capacity。
  • 如果设置的空间小于当前capacity,则缩小空间或不做变化。

resize:

  • 如果有效数据个数大于当前size,若规定了特定值则用特定值填充,否则用数据类型的默认值填充
  • 如果有效数据个数小于当前size,则只保留前size个数据。

我们可以通过以下代码实践这两个函数:

void test2()
{
	vector<int> v1(2,1);
	cout << v1.capacity() << endl;//2
	cout << v1.size() << endl;//2
	v1.reserve(10);
	cout << v1.capacity() << endl;//10
	v1.reserve(2);
	cout << v1.capacity() << endl;//2
	v1.resize(4);
	cout << v1.size() << endl;//4
	v1.resize(2);
	cout << v1.size() << endl;//2
	v1.resize(4, 3);
	cout << v1.size() << endl;//4
}

我们可以把两次扩大size的结果打印出来,如下:

3.3empty函数

empty函数判断vector容器是否为空的函数。

empty:

  • 如果vector容器为空,则返回真
  • 如果vector容器不为空,则返回假。

我们可以写出如下测试代码:

	vector<int> v1(2,1);
	vector<int> v2;
	cout << v1.empty() << endl;//0-->假
	cout << v2.empty() << endl;//1-->真

四.vector迭代器相关函数

4.1begin和end函数

begin()函数返回vector容器的第一个位置

end()函数返回vector容器的最后一个位置的下一个位置。

图解如下:

我们可以使用这两个函数来遍历容器。

void test3()
{
	vector<int> v1(10, 3);
	v1.push_back(4);
	//正向迭代器
	vector<int>::iterator it1 = v1.begin();
	while (it1 != v1.end())
	{
		cout << *it1 << ' ';
		it1++;
	}
	cout<< endl;
}

值得注意的是,如果容器为空的话,则begin()和end()指向同一处空间。 

 4.2rbegin和rend函数

同样的,vector也给我们提供了反向迭代器rbegin和rend。

图解如下:

同样的,我们也可以使用这两个函数来遍历容器

	//反向迭代器
    vector<int>::reverse_iterator it2 = v1.rbegin();
	while (it2 != v1.rend())
	{
		cout << *it2 << ' ';
		it2++;
	}
	cout << endl;

 打印结果如下:

正向:3 3 3 3 3 3 3 3 3 3 4
反向:4 3 3 3 3 3 3 3 3 3 3

五.vector的增删查改

5.1push_back和pop_back函数

push_back函数是用来尾插的;

pop_back函数是用来尾删的。

void test4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(1);
	v1.push_back(1);
	v1.push_back(1);
	print(v1);//为了方便观察,这个函数是自己写的。
	v1.pop_back();
	print(v1);
}

打印结果如下:

1 1 1 1
1 1 1

5.2insert函数

insert函数用于插入数据。

insert函数的函数原型如下:

iterator insert(const_iterator position, const value_type& val);//在position位置插入一个val
iterator insert(const_iterator position, size_type n, const value_type& val);//在positio位置插入n个val
iterator insert(const_iterator position, InputIterator first, InputIterator last);//将迭代区间[first,last)插入position

insert:

  • 第一个函数用于插入一个val。
  • 第二个函数用于插入n个val。
  • 第三个函数用于在某个位置插入一个迭代区间。 
void test5()
{
	vector<int> v1(10,1);
	vector<int> v2(2, 5);
	v1.insert(v1.begin(), 2);//在第一个位置插入一个2
	print(v1);// 2 1 1 1 1 1 1 1 1 1 1
	v1.insert(v1.begin() + 2, 3, 3);//在第3个位置插入3个3
	print(v1);//2 1 3 3 3 1 1 1 1 1 1 1 1 1
	v1.insert(v1.begin(), v2.begin(), v2.end());
	print(v1);//5 5 2 1 3 3 3 1 1 1 1 1 1 1 1 1
}

 5.3erase函数

 erase函数用于删除数据。

erase函数的原型如下:

iterator erase (const_iterator position);//删除position位置处
iterator erase (const_iterator first, const_iterator last);//删除迭代区间

 erase:

  • 第一个函数用于删除指定位置处的数据
  • 第二个函数用于删除迭代区间[first,last)的数据
void test5()
{
	vector<int> v1(10,1);
	vector<int> v2(2, 5);
	v1.insert(v1.begin(), 2);//在第一个位置插入一个2
	print(v1);// 2 1 1 1 1 1 1 1 1 1 1
	v1.insert(v1.begin() + 2, 3, 3);//在第3个位置插入3个3
	print(v1);//2 1 3 3 3 1 1 1 1 1 1 1 1 1
	v1.insert(v1.begin(), v2.begin(), v2.end());
	print(v1);//5 5 2 1 3 3 3 1 1 1 1 1 1 1 1 1
	v1.erase(v1.begin());
	print(v1); //5 2 1 3 3 3 1 1 1 1 1 1 1 1 1
	v1.erase(v1.begin()+1,v1.end()-1);
	print(v1);//5 1
}

5.4find函数

find函数不是vector库中的函数,而是算法库中的函数。

它的功能是在某个迭代器范围内寻找特定值。

算法库的头文件是:

#include <algorithm>

find函数的原型如下:

InputIterator find (InputIterator first, InputIterator last, const T& val);

 我们可以用如下代码使用find函数

	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	vector<int>::iterator p=find(v1.begin(), v1.end(), 3);
    printf("&p",p);

结果:

0000001BCE97F580 

这里有一个需要大家注意的点:

我们无法使用cout打印出迭代器类型的数据,因此我们这里要用C原生的printf函数进行打印。

5.5at和operator[]

我们可以使用at和operator[]来进行访问数据。
如下所示:

void test6() 
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	int len = v1.size();
	vector<int> v(10, 1);

	for (size_t i = 0; i < len; i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
	for (size_t i = 0; i<len; i++)
	{
		cout << v1[i] << ' ';
	}
	cout << endl;
}

结果:

1 2 3 4
1 2 3 4

六.vector的迭代器失效问题

迭代器的主要作用是让算法不用再关心底层的数据结构,其底层实际上就是一个指针,或者是对指针进行了封装,比如vector的迭代器的原生态指针T*

因此迭代器失效,其实就是迭代器对应的指针指向的内容被销毁了,使用一块被释放的空间的后果就是程序崩溃。

会引起其底层空间改变的操作,都有可能会造成迭代器失效。如:resize、reserve、insert、assign、push_back等。

6.1迭代器失效问题的举例

举例1
void test7()
{
	vector<int> v{ 1,2,3,4,5,6 };
	auto it = v.begin();
	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
	 v.resize(100, 8);
	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
	 v.reserve(100);
	// 插入元素期间,可能会引起扩容,而导致原空间被释放
	 v.insert(v.begin(), 0);
	 v.push_back(8);
	// 给vector重新赋值,可能会引起底层容量改变
	v.assign(100, 8);
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

由于我们在扩容的过程中需要经常使用到异地扩容,因此我们每次插入内容都可能会出现异地扩容,这也就导致了我们原来的迭代器失效。

也就是说,vector原来的空间已经被销毁了,我们的it指向一块被销毁的空间,再对it进行使用时,实际操作的就是一块已经被释放了的空间,从而造成了代码的崩溃。

举例2

vs2022环境下:(gcc环境下能跑)

void test8()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));//a是首元素地址,这儿相当于[a,a+4);使用迭代器区间初始化。
	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据,导致pos迭代器失效。
	v.erase(pos);
	cout << *pos << endl; // 此处会导致非法访问
}

 erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。

但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

6.2迭代器失效问题的解决方法

为了删除vector容器中的偶数,现在给出如此一段代码:

void test9()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
		}
		++it;
	}
}

在删除掉it位置处的数据后,vs就认为迭代器失效了。因此我们这段代码依旧是无法跑的过去的。那么我们应该如何修改这段代码以让他跑的过去呢?

我们发现,迭代器失效的原因是因为底层的指针变化,因此我们每次使用前只要给它再赋一个值即可。

因此,我们可以将代码修改为如下:

void test9()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it=v.erase(it);
		}
		else
		{
			it++;
		}
	}
}

6.3vs迭代器简析 

vs环境对迭代器失效的问题检查是远比gcc环境下严格的。

gcc环境下对迭代器做的处理比较简单,

而在vs环境下,在迭代器的底层中采取了打标志的做法。

即,类似于这样的一行代码:

bool flag=true;

在使用迭代器前,vs会检查flag的值,如果flag为false的话,则会报错。

如果对迭代器进行了写操作,那么就会将flag置false。

而我们每次给迭代器赋值,都会使flag置true。

因此,我们在gcc环境下的一些情况中即便不在对空间进行操作后赋值也不会有什么影响。

但是,为了代码不出现bug,我们应养成每次对空间进行操作后,都对要使用的迭代器进行赋值的习惯。

七.后记

有关vector的模拟实现可参考博主的下篇博文。

如果你想更深入的了解vector的相关内容,可参考cpp官网:cpp官网

码字不易,给个点赞收藏叭~~~

  • 33
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 32
    评论
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值