目录
00.引言
01.vector的介绍
类模版
动态分配内存
顺序存储
02.vector的使用
构造函数
迭代器iterator
1.分类:
2.运用:
扩容
1.resize()
2.reverse()
增删查改
1.增加
2.删除
3.查找
4.修改
00.引言
以前我们讲过string类,std::string类负责字符串的存储和操作,它在内部使用动态内存分配来存储字符串,并且会自动扩展和释放内存空间,以适应字符串的长度变化,这种自动分配内存空间使得其非常方便和易用,而且它也提供了非常多的方法来对字符串进行操作,为我们节省了非常多的时间。博客链接:string类的底层实现,string类的介绍与使用
但美中不足的是string类专门用于处理字符串,对于非字符串则无法使用string类,我们也知道模版的概念,一个模版函数可以支持不同类型的参数,一个模版类同样也可以根据不同的数据类型生成不同的类实例,vector就是么一个模版类。哦不,准确的来说,vector是一个用模版类实现的动态数组容器,容器的概念有别于类,后面会写一篇博客来专门讲解。
01.vector的介绍
类模版
"std::vector"实现不同类型数据的管理是通过类模版的机制实现的。当创建一个vector对象时,可以通过模版参数来指定存储的元素类型。使用"<数据类型>"来显式实例化指定存储的元素类型,例如“std::vector<int>”表示存储整数类型的向量,“std::vector<double>”表示存储双精度浮点型的向量……
#include <iostream>
template<typename T>
class vector {
……
};
int main() {
vector<int> myVector1;
vector<double> myvector2;
return 0;
}
动态分配内存
vector实现动态内存分配主要思路用指针跟踪数组元素,并记录当前容量,需要时在堆上重新申请空间进行扩容。
初始分配:初始时,vector分配一块初始大小的内存空间用于存储元素,初始分配的大小通常比较小,甚至可能是0
插入元素:当向vector中插入新元素时,如果当前元素数量小于容量,则直接将新元素放入数组中。如果当前元素数量已经达到了容量,就需要进行内存扩容。
内存扩容:vector会申请一块新的内存空间,通常是原容量的两倍。然后将原有元素复制到新的内存空间之中,并释放原内存空间。这个过程称为“重新分配”或“扩容”。
更新容量和指针:内存扩容完成后,vector会更新容量信息和指向动态数组的指针,以反映新的内存分配情况。
缩小容量:当vector中的元素数量减少到一定程度时,vector容器可能会选择减少其内存空间,以使得容量与元素想适应。
优缺点分析:
优点:1.动态分配内存大小,提高了代码的灵活性和通用性 2.根据实际的数据量进行相应的内存分配,提高了内存的使用效率,节省了内存空间。
缺点:1.由于扩容需要重新开辟内存空间,这会带来一定的性能开销 2.如果内存分配或释放不当,会导致内存泄漏的问题。
顺序存储
vector内部元素在内存中时连续存储的,因此vector容器具有数据结构顺序表的特点
1.可以以O(1)的时间复杂度对vector中的元素进行随机访问.
2.在尾部插入和删除元素的时间复杂度是O(1).
3.在中间位置插入和删除元素的时间复杂度是O(n),因为需要挪动其他元素.
02.vector的使用
在cplusplus网站里有具体的vector使用文档:vector的使用文档,在实际应用中我们需要熟悉一些常见的vector接口,以下列出了一些常见的接口。
构造函数
在使用vector创建对象之前,需要包含头文件“<vector>”。
我们需要调用构造函数来创建对象,vector的构造函数有以下几种:
1.vector(); 无参构造
2.vector(size_type n, const value_type& val = value_type()); 初始化n个val
3.vector(const vector& x); 拷贝构造
4.vector(InputIterator first, InputIterator last); 迭代器初始化构造
代码演示:
#include <iostream>
using namespace std;
#include <vector>
//vector的构造
int TestVector1()
{
vector<int> first; // 无参构造
vector<int> second(4, 100); // 初始化4个值为100的元素
vector<int> third(second.begin(), second.end()); // 迭代器初始化
vector<int> fourth(third); // 拷贝构造
return 0;
}
迭代器iterator
1.分类:
1.begin + end:获取第一个数据位置的iterator,获取最后一个数据的下一个位置的iterator
2.rbegin + rend:获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的 reverse_iterator
这里为什么end和rend分别获取的是下一个位置和前一个位置而不是元素本身呢?考虑到以下原因
1.在使用迭代器遍历容器时,通常是通过比较该迭代器与end迭代器来确定是否到达末尾,如果end()返回的是最后一个元素本身,那么有可能容器中存在多个与末尾元素相同的元素,此时就不好进行判断;而end()返回的是nullptr就很好进行判断了。
2.可以确保在空容器中begin和end返回的是同一位置。
2.运用:
迭代器类似于指针,可以使用‘*’操作符对迭代器进行解引用,使用‘++’操作符对迭代器进行前进
因此迭代器初始化构造还可以通过以下方式:
#include <iostream>
using namespace std;
#include <vector>
int TestVector1()
{
int myints[] = { 16,2,77,29 };
vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));
cout << "容器5的内容是:";
for (auto it = fifth.begin(); it != fifth.end(); ++it)
cout << ' ' << *it;
cout << '\n';
return 0;
}
int main()
{
TestVector1();
return 0;
}
迭代器遍历打印
请看以下代码实例:
void PrintVector(const vector<int>& v)
{
// const对象使用const迭代器进行遍历打印
vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void TestVector2()
{
// 使用push_back插入4个数据
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// 使用迭代器进行遍历打印
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 使用迭代器进行修改
it = v.begin();
while (it != v.end())
{
*it *= 2;
++it;
}
// 使用反向迭代器进行遍历再打印
// vector<int>::reverse_iterator rit = v.rbegin();
auto rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
PrintVector(v);
}
扩容
1.resize()
resize()函数用于调整容器大小,它可以增加或减少容器的元素数量。其具有以下特点:
-
如果新的大小大于当前大小,则向向量中添加新的元素,并使用默认值初始化这些新元素。
-
如果新的大小小于当前大小,则丢弃超出新大小范围的元素。
-
resize()
操作可能导致向量的内存重新分配,因为它可能会增加或减少内存的使用量。 -
通常在需要动态调整向量大小的情况下使用,比如在读取数据时动态添加元素或者在数据处理过程中动态删除元素。
示例:
void TestVector3()
{
vector<int> v;
//向容器内插入元素:
for (int i = 1; i < 10; i++)
v.push_back(i);
v.resize(5);
v.resize(8, 100);
v.resize(12);
cout << "v contains:";
for (size_t i = 0; i < v.size(); i++)
cout << ' ' << v[i];
cout << '\n';
}
2.reverse()
reverse()函数用于预留向量的存储空间,但并不改变向量大小。其有以下特点:
-
reserve()
函数预留了足够的内存空间,以容纳指定数量的元素,但并不会改变向量的大小。也就是说,向量的size()
不会改变,但是capacity()
可能会增加。 -
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()
和 reserve()
的区别在于:resize()
用于调整向量的大小并添加或删除元素,而 reserve()
用于预留向量的内存空间而不改变向量的大小。
增删查改
1.增加
pushback()方法向容器的末尾添加新的元素。
std::vector<int> vec;
vec.push_back(10); // 在向量末尾添加元素 10
insert()方法在指定位置插入新的元素。
std::vector<int> vec = {1, 2, 3, 4};
vec.insert(vec.begin() + 2, 5); // 在位置 2 插入元素 5
2.删除
pop_back()方法删除末尾元素。
std::vector<int> vec = {1, 2, 3, 4};
vec.pop_back(); // 删除末尾元素
erase()方法删除指定位置或范围的元素。
std::vector<int> vec = {1, 2, 3, 4};
vec.erase(vec.begin() + 1); // 删除位置 1 的元素
vec.erase(vec.begin() + 1, vec.begin() + 3); // 删除位置 1 到 2 的元素
3.查找
下标操作符 []
或者 at()
方法访问指定位置的元素。
std::vector<int> vec = {1, 2, 3, 4};
int value = vec[2]; // 获取位置 2 的元素值
迭代器进行遍历和访问向量中的元素。
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 输出向量中的元素
}
4.修改
下标操作符 []
或者 at()
方法修改指定位置的元素的值。
std::vector<int> vec = {1, 2, 3, 4};
vec[2] = 5; // 将位置 2 的元素值修改为 5
迭代器进行遍历和修改向量中的元素。
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it *= 2; // 将向量中的元素值乘以 2
}
以上就是关于vector的介绍和使用说明的有关知识了,欢迎在评论区留言~感觉这篇博客对您有帮助的可以点赞关注支持一波喔~😉