欢迎来到 CILMY23 的博客
🏆本篇主题为:深入探索vector:动态数组的魔力,入门指南
🏆个人主页:CILMY23-CSDN博客
🏆系列专栏:Python | C++ | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题 | 代码训练营
🏆感谢观看,支持的可以给个一键三连,点赞收藏+评论。如果你觉得有帮助,还可以点点关注
前言
在模拟实现完string后,才明白大家为什么都在吐槽string,甚至阅读了一篇大佬的发言
STL 的string类怎么啦?_string类在stl里面吗-CSDN博客https://blog.csdn.net/haoel/article/details/1491219
string 的接口繁多,初次学习的时候眼花缭乱,不禁感叹的是,我也写下了最长的一篇博客(链接)。记得写了好几天。
甚至还阅读了一篇好文
C++面试中string类的一种正确写法 | 酷 壳 - CoolShellhttps://coolshell.cn/articles/10478.html这几篇对加深string都有认识,那接下来我们将接触vector容器,作为经典的容器之一,它又会带给我们什么呢?我们接着往下看。
提示:本篇附赠了没有学过string想直接上手的入门指南
[💫vector 的默认成员函数](#💫vector 的默认成员函数)
[💫vector 的遍历](#💫vector 的遍历)
[💫vector 的扩容机制](#💫vector 的扩容机制)
[💫vector 的对象操作](#💫vector 的对象操作)
[💫vector 中的vector](#💫vector 中的vector)
[💫vector 中的对象数组](#💫vector 中的对象数组)
vector
一、vector介绍
vector文档:
cplusplus.com/reference/vector/vector/https://cplusplus.com/reference/vector/vector/文档当中的介绍我就不像string一样放截图翻译了,说的几点我直接翻译。
- 1.vector是一个顺序(序列)容器,它的表现形式是像可以改变大小(长度)数组一样。文档的第一句话就告诉我们vector是表示可变大小数组的序列容器。
- 文档的第二段话:就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
- 文档的第三段在说vector的本质,它是利用动态数组实现的:本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- **这一段是再说vector的空间是如何分配的:**相对的,vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- .因此,和数组相比,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好。
二、vector的详解
💫vector的接口
vector的接口比起string就没有那么多了,大致接口如下,我们可以看到我们的老伙伴了,构造函数,析构函数,size,还有[]。接下来我们从成员函数开始,一步步学习如何使用吧。
💫vector原型
vector原型如下,第一个参数,class T是一个模板,而第二个是空间配置器,也就是是内存池,所有容器都用内存池来开空间,这样可以提高效率。我们只需要知道,它是用来分配内存即可。
💫vector 的默认成员函数
🍃构造函数(⭐)
cplusplus.com/reference/vector/vector/vector/
进入构造函数的文档界面,我们看到有四个构造函数,其中重点掌握的,也是最常用的,我在图片中已经标出来了。剩下的了解就差不多了。
第一个是默认的构造函数,是无参的,那括号中的又是什么呢?
const allocator_type& alloc = allocator_type()
这个是我们刚刚提到的内存分配,所以不用太在意这一块,之后我们总会接触到的。
第一个无参构造函数,也是我们很经常用的,除此之外还可以使用第四个拷贝构造函数。
使用如下,这一块和string是差不多的。
🍃析构函数
对析构函数我们不必关注太多,稍微看看就行,在学习的时候,我们知道析构函数的功能即可 .
🍃赋值运算符重载
赋值运算符重载,就给了一种形式,
其实际操作如下:
这个应用起来,可比string的文档少的多。
💫vector 的遍历
vector 的遍历和string 是一样的,大家都可以用迭代器,下标加[],以及范围for三种形式。
实际操作:
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;
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
当然因为我们还没有插入任何数据,所以是看不到屏幕上的情况的。 我们可以对v1进行push_back一些数据。
然后就可以显示了。
💫vector 的扩容机制
听说VS下的vector扩容和g++的不一样,我们来试试就知道了。
cpp
// 测试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下的测试:
我们可以发现
在我们的虚拟机上就可以测试出来,代码和上述是差不多的,我就不放出来了,那这里输入的代码如图所示,我们可以看到屏幕给我们展示了vector的2倍扩容。
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会出现,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
💫vector 的对象操作
是否大家记得在我们写string的时候,提到过find函数,如图所示,
但是vector的介绍这里面没有find,它居然写在算法里面了。
这是算法库的文档
- C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/algorithm/?kw=algorithm 我们可以找到这个find函数。
我们还可以通过文档查看value,这段话是说,如果找到了,返回下标,没找到就返回last。这个last 实际上就是我们给的的区间末尾。
所以具体的使用如下:
为什么vector用算法里面的find,而string要用自己的find?
原因可能有以下三点:
1.string写的早
2.string还要支持字串的查找
3.vector只需要找某个值
💫vector 中的vector
以前我们在学习C语言的时候,接触过一个二维数组,我们说二维数组可以看过由一维数组构成的。
我们可以通过一串代码来理解一下vector中的二维表现形式。
cpp
void test1()
{
vector<vector<int>> vv;
// 初始化
vv = { {1, 2, 3},
{4, 5, 6},
{7, 8, 9} };
// 访问二维矩阵的元素
cout << "vv[0][1]: " << vv[0][1] << endl;
cout << "vv[2][2]: " << vv[2][2] << endl;
}
解析:
当我们声明 vector<vector<int>> vv; 时,我们创建了一个 vector 对象 vv ,其中每个元素也是一个 **vector<int>**对象。这种嵌套的结构可以被看作是二维数组。
在这个二维数组中,每个元素 vv[i] 也是一个 vector<int> 对象,表示二维数组的一维数组。
我们可以使用下标来访问和操作这个二维数组。
例如,可以使用 vv[i][j] 来访问二维数组中的特定元素,其中 i 表示行下标,j 表示列下标。
就是按二维数组的方式去理解即可,但是又不一样,这个毕竟中间不一定只存vector。结构体中嵌套一个结构体,实际上用指针模拟实现二维数组的时候也可以这么看。
画图理解:
这里因为是整形类型,所以我写的是int* 的指针,当然实际情况可能并不这样,我们只是大概的去类比一下,稍微画画,理解这个二维vector这个概念。
💫vector 中的对象数组
像上一个小节中的,二维vector其实是一个对象数组的概念。
"vector的对象数组"指的是将多个对象存储在一个 vector 容器中,形成一个对象数组的结构。
在C++中,vector 是一个动态数组容器,可以存储多个对象,并且可以根据需要动态调整大小。每个对象可以是相同类型的,也可以是不同类型的。
当我们将多个对象存储在 vector 中时,就形成了一个对象数组。这个对象数组可以根据需要进行扩展或缩小,并且可以使用下标来访问和操作其中的对象。
三、入门指南
记得在使用 vector 之前包含 <vector> 头文件。
💦vector对象创建
vector<type> name
其中,type 是你要存储在 vector 中的对象的类型,而 name 是你给这个 vector 对象起的名称。例如,如果你想创建一个存储整数的 vector 对象,你可以这样写:
cpp
vector<int> v
同样,如果你想创建一个存储字符串的 vector 对象,你可以这样写:
cpp
vector<string> names
这将创建一个名为 names 的 vector 对象,用于存储字符串类型的数据。
你可以根据需要在程序中创建多个 vector 对象,并使用它们来存储和操作不同类型的数据。
💦vector的插入
🍀尾插
要向 vector 中进行尾插操作,可以使用 push_back() 函数。这个函数将一个元素添加到 vector 的末尾。
cpp
vector<int> numbers;
// 进行尾插操作
numbers.push_back(1);
numbers.push_back(2);
numbers.push_back(3);
运行这段代码,输出将会是 1 2 3。
🍀任意位置插入
要向 vector 中任意插入元素,可以使用 insert() 函数。
insert() 函数有多种用法,可以在指定位置插入一个元素,也可以在指定位置插入一个范围内的元素。
在指定位置插入一个元素:
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 在位置为 index 的位置插入元素 6
v.insert(v.begin() + index, 6);
在指定位置插入一个范围内的元素:
cpp
vector<int> v1 = {1, 2, 3};
vector<int> v2 = {4, 5};
// 在位置为 index 的位置插入 v2 中的元素
v1.insert(v1.begin() + index, v2.begin(), v2.end());
💦vector的常用接口
在 C++ 中,vector 提供了一些常用接口,一起来看看吧。
cpp
vector<int> v;
v.size();//返回容器中元素个数
v.begin()//返回头部迭代器
v.end()//返回尾部+1迭代器
v.empty()//判断是否为空
- v.size():返回 vector<int> 容器中的元素个数。它返回一个无符号整数类型的值,表示容器中元素的数量。
- v.begin():返回一个指向 vector<int> 容器中第一个元素的迭代器。它指向容器的起始位置。
- v.end():返回一个指向 vector<int> 容器末尾元素的下一个位置的迭代器。它指向容器的结束位置。
- v.empty():检查 vector<int> 容器是否为空。如果容器为空,则返回 true;否则返回 false。
💦vector的删除
要从 vector 中删除元素,可以使用 erase() 或 pop_back() 函数。
🍀指定位置删除
使用 erase() 函数删除指定位置的元素:
cpp
vector<int> v = {1, 2, 3, 4, 5}; //这是C++11的写法
// 删除位置为 index 的元素
v.erase(v.begin() + index);
🍀尾删
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 删除最后一个元素
v.pop_back();
💦vector的resize()和reserve()
resize() 和 reserve() 是 vector 类提供的两个函数,用于调整容器的大小和预分配内存空间。
🍀resize()
resize() 函数用于调整 vector 的大小,可以增加或减少元素的数量。
cpp
vector<int> v = {1, 2, 3};
// 增加容器的大小为 5,新增的元素用默认值填充
v.resize(5);
// 减少容器的大小为 2,多余的元素将被删除
v.resize(2);
在上面的代码中,resize() 函数接受一个整数参数,表示要调整的大小。如果调整后的大小比当前大小大,则新增的元素将用默认值填充;如果调整后的大小比当前大小小,则多余的元素将被删除。
🍀reserve()
reserve() 函数用于预分配 vector 的内存空间,以提高性能。
cpp
vector<int> v;
// 预分配至少能容纳 100 个元素的内存空间
v.reserve(100);
在上面的代码中,reserve() 函数接受一个整数参数,表示要预分配的内存空间大小。这样做可以避免频繁的内存重新分配,提高 vector 的性能。
需要注意的是,resize() 和 reserve() 函数都会影响 vector 的大小和容量。大小是指容器中实际存储的元素数量,而容量是指容器当前分配的内存空间大小。
🛎️感谢各位同伴的支持,本期C++专题就讲解到这啦,下期我们将进入模拟实现vector,如果你觉得写的不错的话,可以给个一键三连,点赞,收藏+评论,可以的话还希望点点关注,若有不足,欢迎各位在评论区讨论。