3)vector类对象的常见容量操作
接下去我们来看看vector类对象的常见容量操作
① size
- 首先的话来讲讲
size()
,其表示为当前容器中的数据个数
void test_vector6() { vector<int> v(10, 1); cout << v.size() << endl; }
- 我们来看到这个执行结果,初始化时我们为容器中放入了10个1,那么其
size
即为10
② capacity
- 对于【capacity】来说,就是容量大小,这里可以看到其与capacity是一同增长的,也为10
- 我们也可以到 Linux平台 下来进行观察,发现也是一样的结果
以上这一点设计到【vector】的默认扩容机制
下面是我们的测试代码
// 测试vector的默认扩容机制 void TestVectorExpand() { size_t sz; vector<int> v; sz = v.capacity(); cout << "making v grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } }
- 通过运行结果我们可以发现,在VS下的扩容机制是呈现 1.5 进行增长的,其STL是【P.J.版本】
- 但是呢,在 Linux 下却始终是呈现的一个2倍的扩容机制,其STL是【SGI版本】
③ empty
- 再来看看【empty】接口,当一开始进在初始化后是为空,但是在插入数据后就不为空了
下面两个接口比较重要一点,我会着重讲解
④ reserve
- 首先的话是【reserve】,它的主要功能是 ==开空间,避免频繁扩容==
void TestVectorExpandOP() { vector<int> v; size_t sz = v.capacity(); v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容 cout << "making bar grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } }
- 通过测试我们可以看到,当提前开好空间后,就可以避免频繁地去扩容了
⑤ resize
【resize】的功能则是 ==开空间 + 初始化,并且填上默认值==
- 这一块我们要通过调试来进行观察,首先看到没有
resize
的样子
- 然后我们传递一个值进去看看,看到调试窗口中的
size
发生了变化,而且新增了3个为0的数据值
v.resize(3);
对于【reserve】和【resize】,我这里还要再讲一个同学们日常中容易犯的错误
- 接下去请读者观察一下下面这段代码,然后看看其中有什么问题?
void test_vector8() { vector<int> v1; v1.reserve(10); for (size_t i = 0; i < 10; i++) { v1[i] = i; } }
然后我将程序运行起来,发现报出了错误❌
💬 有同学说:感觉这代码也没什么错呀?怎么会有错误呢?
- 大家要关注前面的
reserve(10)
,我们在上面说到对于【reserve】而言只是做的扩容而已,即只变化capacity
,而不会变化size
- 另一点,对于
v1[i]
我们上面在讲元素访问的时候有说到过,这是下标 + []
的访问形式,在出现问题的时候会直接给出断言错误。因为这里我们在【reserve】的时候只是开出了指定的空间,但size
还是为0,此时去访问的时候肯定就出错了
正确的改进方法应该是像下面这样的
- 如果我们要使用
下标 + []
的形式去访问元素的话,就需要开出合适的size
大小,才能在访问的时候不会造成越界问题
vector<int> v2; v2.resize(10); for (size_t i = 0; i < 10; i++) { v2[i] = i; }
- 我们通过调试来观察一下吧
- 或者呢,我们也可以写成下面这种形式。如果有同学还是要使用【reserve】的话就不要使用
下标 + []
的形式了,而是使用【push_back】的方式去不断尾插数据,因为在不断尾插的过程中就会去做一个扩容,这一点马上就会讲到
vector<int> v3; v3.reserve(10); // 提前开好空间,减少扩容,提高效率 for (size_t i = 0; i < 10; i++) { v3.push_back(i); }
- 同样,我们通过调试来看看
4)vector类对象的修改操作
接下去呢,我们来说说有关vector类对象的修改操作
函数名称 | 接口说明 |
push_back(重点) | 在字符串后尾插字符c |
pop_back(重点) | 尾删 |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
① push_back
这个接口的功能很明确,就是在尾部插入数据
- 假设,我们这里采取
string
类作为【vector】的内置类型,然后通过三种形式往里面插入数据:第一种是构造出具体的对象,第二种采取的是匿名对象,第三种采取的则是单参数的构造函数所引发的 ==隐式类型转换==
void test_vector9() { vector<string> v; string name1("张三"); v.push_back(name1); v.push_back(string("李四")); v.push_back("王五"); // 单参数的构造函数支持隐式类型转换 }
- 一样,通过调试来看看
② pop_back
对于【pop_back】来说,很明显就是去尾删最后一个元素
- 以下是代码及其运行结果
v.pop_back();
③ insert
对于【insert】这个接口来说,重载的方法有很多,读者可以自己下去都试试看,我这里只讲解部分
- 还是延续上面的,我们在
begin()
这个位置插入一个数据,也就相当于是头插
v.insert(v.begin(), "刘琦");
④ erase
有插入,那一定有删除,我们来看看【erase】
- 这里看到有两个重载形式,一个是传递迭代器,另一个则是传递迭代器区间
- 这里我们试试传递一个迭代器,但是呢不是头删,而是删除头部的后一个元素
v.erase(v.begin() + 1);
💬 就上面这样没有难度,但是现在我若是想要删除这个容器内指定的数据呢?该如何去进行操作
- 对于这个操作而言,确实没有实际可用的方法而言是无法做到的,还记得我们在 string中所学习的find()接口 吗?其可以帮助我们去找到指定的内容。但是呢?在【vector】的修改操作中,我们并没有发现
find
这个接口
⑤ find
其实对于这个接口而言,是封装在了
<algorithm>
这个头文件中,称作是一种算法
- 我们一起来看看具体的文档是怎么说的
- 有了它相助后,我们要去删除一个指定的数据就容易多了,传入指定的搜索区间和要查找的值,若是返回的迭代器位置没有到达末尾的话,代表找到了这个值,我们去删除这个迭代器即可
vector<int>::iterator pos = find(v.begin(), v.end(), 1); if (pos != v.end()) { v.erase(pos); }
- 来看一下执行结果
💬 那我这个时候再拔高一下难度,说我要删除这个容器中所有的【3】,该怎么去完成呢?
- 那既然是一直找的话,我们就需要通过循环来实现,不断地去更新这个
pos
值,然后找到一个就去删除一个
auto pos = find(v.begin(), v.end(), 3); while (pos != v.end()) { v.erase(pos); pos = find(pos + 1, v.end(), 3); }
- 但是运行起来呢却发现程序崩溃了,这里就涉及到一个 ==迭代器失效的问题==。我们在后面模拟实现的时候再去介绍
三、总结与提炼
最后来总结一下本文所学习的内容
- 本文我们重点讲到的是STL中的 vector类,首先我们初步认识了这个类,逐个地去了解了它的一些接口函数,包括【默认成员函数】、【访问及遍历操作】、【常见容量操作】、【修改操作】这些,对于 vector 来说它不像 string 那样有一百来个接口,而是只有几十个,我又精简地挑了一些比较重要的来进行详述,我所讲到的希望读者都可以学会使用并且搞懂
上就是本文要介绍的所有内容,感谢您的阅读🌹🌹🌹