STL 大锅炖(一):什么是 STL
STL 大锅炖(二):String 容器
STL 大锅炖(三):Vector 容器
个人博客内有更多精彩文章欢迎走过路过的大佬们点踩~ 👉 小北北北北的秘密小窝
文章目录
前言
STL 大锅炖系列到目前为止已经更新了两期,如果各位小伙伴对 STL 有兴趣,亦或是想要提升自己的话可以通过文章开头的引导查看 STL 系列的其他文章
本节中为大家介绍的是一种十分常见的 STL 容器:Vector
,头文件为 <vector> 。其实有接触过数据结构的小伙伴大都了解过 vector 容器的本质:动态数组。其在拥有数组顺序序列特性的同时,避免了数组的许多缺点
一、什么是 Vector
vector 的意思是 向量
,正如其名所言,该容器与向量组有着十分相似的地方,其是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。我们可以简单的认为向量是一个能够存放任意类型的动态数组。
动态数组与常见的数组不同的地方在于其 空间的应用 与其 空间的灵活性。
常见的数组是 静态分配内存
,在使用时需要进行空间的预分配,该空间一旦分配就 无法更改,倘若出现数组空间不够用的情况就只能重新开辟一块更大的空间后将数据转移,再将原数组的内存空间释放,在使用与维护上都十分不便。
动态数组,即 动态分配内存空间
的数组,随着后续元素的不断加入,其内部的扩充机制会 自动扩充 内存空间以容纳新的元素。因此 vector 的应用对于内存空间的合理利用与其应用的灵活性均有着较大的帮助。
二、Vector 的扩容机制
往 vector 中添加元素时,如果空间不够将会导致 自动扩容
。vector 有两个属性:size 和 capacity,size 表示已经使用的数据容量,capacity 表示数组的实际容量,包含已使用的和未使用的。
1. 扩容机制
在这里为大家解释一下使 vector 可以进行动态扩容的 扩容机制:
- 当 vector 的 size 与 capacity 相等时(即满载)则触发扩容机制:
- 完全弃用现有的内存空间,重新申请更大的内存空间。
- 将旧内存空间中的数据,按原有顺序移动到新的内存空间中。
- 将旧的内存空间释放。
- 新的内存空间一般是旧空间的 1.5 倍或者 2 倍,此处的 1.5 或者 2 被称为 扩容因子,不同的系统实现的扩容因子也有所不同。
- 由于扩充机制是由 vector 容器本身进行维护,所以该机制对于用户而言是 透明 的(用户无法感知扩容机制)。
2. 扩充因子
关于扩充因子的数值在界内的说法不一,但较为统一的一点是:我们希望在通过前几次的空间扩容后,当前内存空间存在被下一次扩容时利用(对于内存与 cache 而言)的可能性。
从时间上考虑扩容因子越大越好,从空间上考虑扩容因子越小越好,那么有没有一个数能够同时协调好空间与时间?通过理论得出能够协调好空间与时间的扩充大小符合 X(n-2) + X(n-1) = X(n)
,即 1,2,3,5,8,13,21,34,55 … 看到这里想必有小伙伴会对该序列十分熟悉,是的,斐波那契数列。
于是 当扩充次数趋于无限大时取极限,最佳的扩容因子也就是那个最完美的数:黄金分割率 1.618,在不同的系统中扩充因子越接近这个数值则其空间时间上的效率都会越高,为了方便计算根据不同的系统差异取 1.5 或 2 为最佳扩充因子。
三、Vector 的迭代器
在介绍 vector 的迭代器之前,我们需要知道一个概念:迭代器 != 指针 ,但 vector 是少数的特例。
vector 维护着一个线性的内存空间,所以无论其内部存储的是何种的数据类型,普通指针均可以作为 vector 的迭代器,正因其内存空间的线性使其支持随机存储(与数组原理相同),因此 vector 所提供的是 随机访问迭代器(Random Access Iterators)。
Tips:
需要注意的是,当 vector 进行自动扩容后,原迭代器(指针)将会 失效,因为其指向的是被遗弃的内存空间,而新数据已被转移至新的内存空间,需要重新定义迭代器(指针)。
容器的遍历操作均可以采用迭代器(Iterator)进行,下面介绍两种常用的迭代器使用方式:
vector<int> myVector; // 定义一个 int 型 vector
/* 指定类型的迭代器进行遍历:vector<T>::iterator it */
for(vector<int>::iterator it = myVector.begin(); it != myVector.end(); it++)
{
cout << (*it) << endl; // 打印相应数据
}
/* 实际使用时常采用自动类型推断:auto 关键字进行迭代器的定义:auto it */
for(auto it = myVector.begin(); it != myVector.end(); it++)
{
cout << (*it) << endl;
}
/* 在 vector 容器中可以直接采用数组的方式 [],进行数据的随机读写 */
for(int i = 0; i < length; i++)
{
cout << myVector[i] << endl;
}
四、Vector 的常用操作
接下来介绍的是 vector 的一些 基本操作
如:赋值、比较、数据存取、插入 和 删除等。
1. 构造函数
以下是常用的几个构造函数:
vector<T> v; // 采用模板实现类实现,默认构造函数
vector(n, elem); // 构造函数将 n 个 elem 拷贝给本身
vector(v.begin(), v.end()); // 将 v.(begin(), end()) 区间中的元素拷贝给本身
vector(const vector &vec); // 拷贝构造函数
2. 赋值操作
对于赋值操作,重载等号 与 assign 方法 是容器中均有的赋值操作:
assign(v.begin(), v.end()); // 将 v.(begin(), end()) 区间中的数据拷贝赋值
assign(n, elem); // 将 n 个 elem 拷贝赋值给本身
vector& operator=(const vector &vec); // 重载等号操作符
swap(vec); // 将 vec 与本身的元素互换
3. 大小操作
以下是常用几个的有关大小操作:
size(); // 返回容器中元素的个数
empty(); // 判断容器是否为空
/* 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。*/
resize(int num);
/* 重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
如果容器变短,则末尾超出容器长>度的元素被删除。*/
resize(int num, elem);
capacity(); // 返回容器的容量
reserve(int len); // 容器预留len个元素长度,预留位置不初始化,元素不可访问
4. 存取操作
对于 vector 可以像数组一样采用 [ ] 进行数据的访问,也可以使用容器固有的 at 方法:
at(int index); // 返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[]; // 返回索引 idx 所指的数据,越界时,运行直接报错
front(); // 返回容器中第一个数据元素
back(); // 返回容器中最后一个数据元素
5. 插入和删除
以下是常用的几个插入与删除操作:
insert(const_iterator pos, int count,ele); // 迭代器指向位置pos插入count个元素ele
push_back(ele); // 尾部插入元素 ele
pop_back(); // 删除最后一个元素
// 删除迭代器从start到end之间的元素
erase(const_iterator start, const_iterator end);
// 删除迭代器指向的元素
erase(const_iterator pos);
clear(); //删除容器中所有元素
五、Vector 实现二维数组
在 Vector 中可以使用 resize()
函数实现二维数组:
- 定义一个二维数组并初始化其 行(row)
- 对二维数组进行 resize() 内存重分配 定义 列(col)
vector<vector<int>> array(row); // 采用构造函数初始化行数 row(row不能少)
for(int i=0; i<row; i++) // 初始化一个 row*col 的二维数组
{
array[i].resize(col); // 内存重分配
}
小结
在开发过程时,数组是十分常见的数据结构,但它的内存特征限制了其灵活性,通过自动扩容机制可以对其缺点进行一定程度上的避免,vector 作为一种容器将所有的细节全都封装后给用户留下了各种函数接口,在使用与维护上具有得天独厚的优势,作为一种十分常见的 STL 容器也是我们需要熟练掌握与应用的。
以上便是有关 STL 中关于 Vector 的概述,如果本文对你有帮助,不如分享给朋友或留下评论支持一下作者 ~