💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!
文章目录
- 前言
- 一、文档介绍[vector](https://legacy.cplusplus.com/reference/vector/vector/?kw=vector)
- 1.1vector的接口
- 1.2vector构造器
- 1.3vector遍历和修改数据操作
- 1.4vector的空间增长问题
- 1.5vector的插入和删除操作
- 1.6vector的排序操作
- 1.7vector的查找操作(迭代器失效)
- 二、总结
前言
今天我们开始讲解新的知识,关于vector的使用,这个容器使用起来非常的香,他可以做很多事情,也类似于一种数组的实现,但是他比数组更加的高级,让我们一起来看看vector是怎么使用的吧
一、文档介绍vector
对于vector我们目前可以简单理解为存放任意类型的可变数组。也可以像数组那些去使用,但是又被数组的用法多。
1.1vector的接口
想要学会vector的使用,我们需要熟悉它的接口,他能更好的运用它,接下来我就带大家来学习再常用的接口以及使用方法
通过文档我们发现vector实现了这些的接口,有些接口大家看到应该就东什么意思,因为我们刚刚学会string类,接口名都是一样的,我们想要使用一个容器,就必须先实例化出对象,就要先掌握它对应的构造器
1.2vector构造器
来看看这几种构造器的使用:
//1.默认构造器
vector<int> v1;
//2.初始化的值要符合定义的类型
vector<int> v2(4,100);
//3.使用迭代器或者指针都行
string s;
int a[4] = { 0 };
vector<int> v3(s.begin(), s.end());
vector<int> V3(a,a+4);
//4.用另一个vector对象创建另一个
vector<int> v4(v3);
我们一共就这四种,再下篇的模拟实现中,我会带大家都实现一遍,让大家体会里面的原理
1.3vector遍历和修改数据操作
(1)iterator
我们迭代器的种类非常多,博主就介绍其中一种,其余的使用是类似的,这个迭代器的使用和前面string使用迭代器是一样的
vector<int> v2(4, 100);
vector<int>::iterator it = v2.begin();
while (it != v2.end())
{
cout << (*it)*=2<<" ";
it++;
}
cout << endl;
(2)operator[]下标的方式
就是遍历数组一样的道理
vector<int> v2(4, 100);
for (size_t i = 0; i < v2.size(); i++)
{
v2[i]=20;
cout << v2[i] << " ";
}
cout << endl;
(3)范围for
通过前面string的学习,我们知道范围for是基于迭代器的存在去使用的,既然我们有了迭代器的存在,那看看范围for怎么使用
vector<int> v2(4, 100);
for (auto& i : v2)
{
i=50;
cout << i << " ";
}
cout << endl;
我们还是喜欢下标的方式来遍历vector,毕竟用的时候最长。
1.4vector的空间增长问题
对于接口什么意思我就不做过多的解释了,和string类的意思一样,直接来看使用
vector<int> v2(4, 100);
cout << "一开始的size:" << v2.size() << endl;
cout << "一开始的capacity:" << v2.capacity() << endl << endl;
cout << "进行resize改变有效字符个数"<<endl;
v2.resize(6);
cout << "resize(6)后的size:" << v2.size() << endl;
cout << "resize(6)后的capacity:" << v2.capacity() << endl << endl;
cout << "进行reserve改变容量" << endl;
v2.reserve(15);
cout << "reserve(15)后的size:" << v2.size() << endl;
cout << "reserve(15)后的capacity:" << v2.capacity() << endl << endl;
cout <<"empty的测试:" << v2.empty() << endl;
注意: 我们来看一段代码
vector<int> v1;
v1.reserve(10);
for (size_t i = 0; i < 10; i++)
{
v1[i] = i;
}
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
我们一开始创建一个vector对象是没有空间的,我们进行扩容之后,想给将vector里面的数据改成0-9,结果运行却崩溃了,原因是:使用[]来进行访问vector里面的数据会有检查,是通过size的范围来确定的,通过上面的演示,你改变capacity的值,并不会改变size的,但是改变size的值有可能改变capacity的值,所以将reserve换成resize就可以了
使用迭代器和范围for都不行,他们的终点都是以size为准的
对于扩容每个平台的机制也是不一样的,再vs上市1.5倍扩容,再Linux上是二倍扩容,这个大家下去可以自己测试一下
1.5vector的插入和删除操作
我们发现有尾插和尾删,也有任意位置的插入和删除,再底层尾插和尾删是复用任意认知的插入和删除,再模拟实现·的时候再给大家介绍吧,我们先来看看这几个怎么使用吧。
对于push_back和pop_back
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);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout<<endl;
v1.pop_back();
v1.pop_back();
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
对于insert
- 第一种传一个迭代器表示再vector的哪个位置插入一个元素
vector<int> v1;
v1.insert(v1.begin(), 1);
v1.insert(v1.begin(), 2);
v1.insert(v1.begin(), 3);
v1.insert(v1.begin(), 4);
v1.insert(v1.begin(), 5);
v1.insert(v1.begin(), 6);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
- 第二种传一个迭代器表示再vector的哪个位置插入n个元素
v1.insert(v1.begin(), 1,100);
v1.insert(v1.begin(), 2,200);
v1.insert(v1.begin(), 3,300);
v1.insert(v1.begin(), 4,400);
v1.insert(v1.begin(), 5,500);
v1.insert(v1.begin(), 6,600);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
- 第三种是传其他迭代的中间的元素插入到任意位置
vector<int> v1(4,100);
int a1[4] = { 1,2,3,4 };
v1.insert(v1.begin() + 1, a1 + 1, a1 + 3);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
把数组的第二和第三个元素插入到vector的第一个元素后面
对于erase
- 第一种是任意位置的元素
vector<int> v1(4, 100);
v1.erase(v1.begin());
- 第二种是从first位置开始删除到last位置
vector<int> v1(4, 100);
v1.erase(v1.begin()+1,v1.begin()+3);
这两种非常简单,我就不做运行演示了
1.6vector的排序操作
我们的vector也可以进行排序,它有对应的算法sort函数,对应的头文件是 < algorithm >
因为sort不是给单独的一个容器进行使用的,所以不能当成vector本身的成员函数,来看看vector怎么进行排序:
升序:
vector<int> v1;
v1.push_back(1);
v1.push_back(3);
v1.push_back(4);
v1.push_back(6);
v1.push_back(5);
v1.push_back(0);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
sort(v1.begin(), v1.end());
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
降序:
(1)这就要看我们第二个sort函数了,第三个参数是仿函数
我们定义仿函数greater<int> g
greaer具有大于的意思,前面一个比后面一个大,哪不就是降序吗??
(2)反向迭代器
通过仿函数和反向迭代器也可以实现升序,大家下来自己测试看看
1.7vector的查找操作(迭代器失效)
我们的查找函数也不是vector特有的函数,查找函数也和sort一样再算法里面,我们来看看文档里面怎么描述的
要注意:找的到就返回对应的迭代器,找到就返回此迭代器的下一个位置,迭代器都是左闭右开的,所以找不到是返回的pos=v.end()
代码演示:
vector<int> v1;
v1.push_back(1);
v1.push_back(6);
v1.push_back(4);
v1.push_back(5);
v1.push_back(3);
v1.push_back(0);
/// <summary>
///我们尝试找到元素6然后删除
vector<int>::iterator pos = find(v1.begin(), v1.end(),6);
v1.erase(pos)
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
我们来看如果有多个重复的数字呢??
我们为了增加效率每次就不从头开始找了,我们发现上面这个带啊吗运行后,就会崩溃,为什么会这样呢??原因就是迭代器失效了
为什么会出现这种情况呢??原因是迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
- 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。因为一发生扩容行为就会释放之前的空间,开辟一个新的空间。
有的人说我先把空间开的足够大,只要不扩容不就行了,可以说是可以的,但是危险性太大了,万一再公司程序有天数据变得很大,还是要扩容,那个时候再修改就来不及了
那我们刚才的代码是删除,不存在扩容的情况啊,为什么也会造成迭代器失效的问题呢???erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
解决办法: 在使用前,对迭代器重新赋值即可
细心读者应该发现,我上面提到的那些可能会出现迭代器失效的接口都有一个迭代器接口,这个返回值就是新空间的迭代器,前面的代码这样修改就行了:
auto pos = find(v1.begin(), v1.end(),6);
while (pos != v1.end())
{
v1.erase(pos);
pos= find(pos+1, v1.end(), 10);
}
现在和大家说这个问题大家应该可以理解,等博主实现模拟实现的时候,再好好跟大家说说,下一篇我将讲解两个题目,让大家看看vector的好处,再下下篇讲模拟实现
二、总结
相信大家对vector的基本使用都掌握了,后面我将会带大家把上面的函数都模拟实现一遍·,这样大家就能更好的理解底层是怎么处理问题的。我们今天的内容就先讲到这里。