本片要分享的是关于STL中Vector的内容,Vector的内容于string非常相似,只要会使用string那么学习Vector时会非常流畅。
目录
1.vector介绍
2.vector的简单实用
2.1.简单的无参构造
编辑2.2.简单带参构造
2.3.迭代器区间初始化
2.4.vector的遍历
2.5.vector插入数据
2.6.扩容机制
不同平台扩容机制
reverse
resize
1.vector介绍
官方的简介是vector是由一个动态增长数组实现的顺序容器,其实再简称一点就是顺序表。
以下是vector的组件
默认成员函数
迭代器
与容量相关的函数
与访问数据相关的函数
与修改容器数据相关的
可以看到上面有我们在string中就接触过的一些函数,比如push_back,下标访问时的operator[],测量长度的size,等等
所以这也是我们学习vector比较容易的原因,同时vector在设计上也基于string有了一些改进,在内部函数的设计方面也更加合理了一些;
2.vector的简单实用
2.1.简单的无参构造
我们上代码来观察,先从最最简单的初始化构造来开始
如上是一个vector的无参构造
首先我们需要包含vector的头文件
其次我们在定义的时候需要将容器实例化, 就是规定我们的数据类型;
2.2.简单带参构造
#include<iostream>
#include<vector>
using namespace std;
void test_vector1()
{vector<int> v1; vector<int> v2(10,0);
}
int main()
{}
观察我们构造的v2,其中有两个参数,那这样的带参构造就是开辟是个空间,并且都初始化为0。
对比C语言我们不仅需要开一个数组,还需要memset,非常的麻烦;
2.3.迭代器区间初始化
#include<iostream>
#include<vector>
using namespace std;
void test_vector1()
{vector<int> v1; vector<int> v2(10,0); vector<int> v3(v2.begin(), v2.end());}
int main()
{test_vector1();return 0;
}
观察v3的初始化方式,我们使用了v2的迭代器的起始位置和末尾的位置
以上是相同容器的迭代器区间的初始化构造,那不同容器之间的初始化构造呢;
#include<iostream>
#include<vector>
using namespace std;
void test_vector1()
{vector<int> v1; vector<int> v2(10,0); vector<int> v3(v2.begin(), v2.end());string s("hello world");vector<int> v4(s.begin(), s.end());}
int main()
{test_vector1();return 0;
}
那当然也是可以的,可以看到我们在初始化v4的时候使用了字符串s的区间,也能完成初始化,但是我们需要注意的是这里的底层涉及到隐式类型转换,所以才能初始化成功。
2.4.vector的遍历
我们在上面的介绍中就可以看到vector读取数据时可以采用[],我们不妨将初始化后的v3进行遍历
void test_vector2()
{vector<int> v2(10, 0);vector<int> v3(v2.begin(), v2.end());for (size_t i = 0; i < v3.size(); i++){cout << v3[i] << ' ';}cout << endl;
}
int main()
{test_vector2();return 0;
}
以下 是输出结果
可以看到我们使用v2迭代器区间初始化的v3输出的结果和我们想要的结果相同。
这里使用了for循环和[ ]对数据进行读取,还可以使用迭代器进行访问。
void test_vector2()
{vector<int> v2(10, 0);vector<int> v3(v2.begin(), v2.end());for (size_t i = 0; i < v3.size(); i++){cout << v3[i] << ' ';}cout << endl;vector<int>::iterator it = v3.begin();while (it != v3.end()){cout << *it << ' ';++it;}cout << endl;
}
同样的我们首先要在vector类中声明并定义迭代器it,将v3的begin的位置给it,在while循环中依次将it解引用并输出,再对it进行++迭代,此时就完成了迭代器的遍历;
第一行的输出结果为for循环;第二行的结果为迭代器,他们都可以进行遍历;
在这里需要提醒大家的是迭代器中的begin或是end等等是指向数据的位置
所以这里我们可以将迭代器的功能理解为指针,遍历时将其解引用即可得到数据。
2.5.vector插入数据
如下是涉及到修改内容的函数
首先是尾插(push_back)
void test_vector8()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << ' ';}cout << endl;
}
int main()
{test_vector8();return 0;
}
结果不出所料,和我们在string中学到的插入方式相同;
同样的也有中间插入的方式insert,
可以看到有很多种插入的方式,这里简单使用
可以看到在begin 的位置之前插入了0;
那如何在以上数据的3的前面插入想要的数呢?
auto it = find(v.begin(), v.end(), 3);if (it != v.end());{v.insert(it, 30);}for (auto e : v){cout << e << ' ';}cout << endl;
这里我们就需要通过find来配合使用insert函数来查找并插入,结果如上;
2.6.扩容机制
不同平台扩容机制
接下来是有关容量的函数
我们首先来研究vector的扩容机制;
在string中我们了解到可以插入很多数据时,系统会自动扩容,在vector中也同样如此;我们用代码来了解一下vector的扩容机制
void test_vector5()
{size_t sz;vector<int> v;sz = v.capacity();for (size_t i = 0; i < 100; i++){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed" << sz << endl;}}
}
可以看到我我们在循环中给v插入数据,如果v的最大容量和插满数据时相同,系统就会自动扩容,此时我们再改变容量,并且标识出容量已经改变,那此时运行结果会是怎样呢?
我们可以看到大概是以原先容量的1.5倍进行扩容。
同样的代码我们在Linux系统下使用g++编译会有什么效果
可以看到对比vs环境下的1.5倍扩容,g++使用的是二倍扩容
reverse
其中还有reserve函数,他的作用是开辟空间,还是如上代码,我们使用reserve尝试一下
void test_vector6()
{size_t sz;vector<int> v;v.reserve(100);sz = v.capacity();for (size_t i = 0; i < 100; i++){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed" << sz << endl;}}
}
我们在它扩容之前使用reserve函数提前开好空间,运行结果如下
可以看到之前的扩容过程都不见了,原因是我们在扩容之前使用了reverse提前开了空间,将capacity修改成我们想要的,即可跳过在for循环中一边插入数据一边扩容的情况。
resize
resize不同于reserve的是,resize不仅可以改变capacity的大小,同时也可以改变size的大小,还是上段同样的代码,这里将reserve修改为resize来观察结果,
resize不仅可以修改容量的大小,还可以修改其本身的长度,这里的运行结果是在resize后的数据再进行插入,也就是说将两端数据接到一起。
还需要注意的一种情况如下
我们使用reserve直接开辟空间,然后直接去访问(利用for循环插入数据) reserve开辟的空间并打印是否可行呢?
结果是不行的,原因是这里访问v使用的是[ ],而在[ ]之前的模拟实现中,也就是它的底层逻辑是有assert断言的,条件是访问的下标必须小于size,而reserve只能修改capacity,不能修改size,所以就会报错。
那使用resize即修改size也修改capacity会怎样呢?
可以看到这样操作就很丝滑了。
以上就是本次要分享的内容,在vector中还有一些不常用的函数在这里没有深入分析到,还请感兴趣的同学们自行尝试,如果对你有所帮助还请多多三连支持,感谢您的阅读。