1.vector的介绍
在C++中,vector
是标准模板库(STL)中的一个动态数组类,属于<vector>
头文件,如果放在c语言当中,就是我们所熟知的数组,不过,c++将其封装成了一个类,不仅能存放char类型、int类型、浮点型、甚至自定义类型等,vector也包含了许多相关的接口,因此,vector要比c语言的数组复杂许多
2.vector的使用
学习像vector等标准模板库的内容时,强烈建议看c++文档,vector在实际中非常的重要,在实际中熟悉常见的接口有以下部分
2.1vector的定义
(costructor)构造函数声明 | 接口说明 |
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
代码示例:
#include<vector> //使用vector需要包含此头文件
#include<iostream>
using namespace std; //对于初学者而言建议使用此头文件,减少了空间域的展开代码int main()
{
vector<int> arr1(10); //创建了一个int类型的10个元素的数组arr1,未进行初始化
vector<int> arr2(5, 5); //创建了一个int类型的5个元素的数组arr2,所有元素均初始化为5
vector<int> arr3(arr2); //使用arr2拷贝构造了arr3
auto it1 = arr2.begin(); //迭代器
auto it2 = arr2.end();
vector<int> arr4(it1, it2); //使用arr2的迭代器进行构造arr4,构造范围是it1到it2
return 0;
}
2.2 vector iterator 的使用
iterator的使 用 | 接口说明 |
begin + end (重点) | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下 一个位置的iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置 的reverse_iterator |
begin 和 end 对应的是正向迭代器
rbegin 和 rend 对应的是反向迭代器
图解:
代码示例:
#include<vector>
#include<iostream>
using namespace std;int main()
{
vector<int> arr1(10);
for (int i = 0; i < 10; i++)
{
arr1[i] = i + 1;
}
vector<int>::iterator it = arr1.begin();
auto it1 = arr1.begin(); //与上一行意义相同,使用auto减少了代码量while (it != arr1.end()) //使用正向迭代器进行遍历
{
cout << *it << " ";
++it;
}
cout << endl;
auto rit = arr1.rbegin();
while (rit != arr1.rend()) //使用反向迭代器遍历
{
cout << *rit << " ";
++rit;
}
cout << endl;for (auto a : arr1) //使用范围for遍历,实际上底层调用的还是迭代器
{
cout << a << " ";
}
}
2.3 vector 空间增长问题
相关接口:
容量空间 | 接口说明 |
size() | 获取数据个数 |
capacity() | 获取容量大小 |
empty() | 判断是否为空 |
resize()(重点) | 改变vector的size(甚至capacity) |
reserve()(重点) | 改变vector的capacity |
代码示例:
#include<vector>
#include<iostream>
using namespace std;int main()
{
vector<int> arr(10, 1);
cout << arr.size() << endl; //获取arr的有效元素个数
cout << arr.capacity() << endl; //获取arr的容量
cout << arr.empty() << endl; //判断arr是否为空arr.resize(8); //改变arr的有效个数,给定的值小于有效元素个数,则减小有效元素个数
for (auto a : arr)
{
cout << a << " ";
}
cout << endl;arr.resize(12, 4); //给定的值大于有效元素个数,则使用“4”进行填充,必要时会自动扩容
for (auto a : arr)
{
cout << a << " ";
}
return 0;
}
需要了解的细节:
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2 倍增长的
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代 价缺陷问题
- resize在开空间的同时还会进行初始化,影响size
对于vector的扩容机制,c++并没有明确的规定,因此取决于编译器,如果想测试一下编译器的扩容机制,可以使用下面这段代码
#include<vector>
#include<iostream>
using namespace std;
// 测试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';
}
}
}
int main()
{
TestVectorExpand();
return 0;
}
在VS2022下运行:
可以明显看出,这里是按1.5倍进行扩容的
2.4 vector 增删查改
vector增删查改 | 接口说明 |
push_back(重点) | 尾插 |
pop_back(重点) | 尾删 |
find | 查找(算法模块实现,非vector成员接口) |
insert | 在position之前插入value |
eraser | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[](重点) | 像数组一样访问其元素 |
代码示例:
#include<vector>
#include<iostream>
using namespace std;int main()
{
vector<int> arr(0); //创建一个空数组
arr.push_back(1); //尾插数据"1"
arr.push_back(2);
arr.push_back(3);
arr.push_back(4);
arr.push_back(5);for (auto a : arr)
{
cout << a << " ";
}
cout << endl;arr.pop_back(); //尾删数据
for (auto a : arr)
{
cout << a << " ";
}
//for (int i = 0; i < 4; i++) //像数组一样访问
//{
// cout << arr[i] << " ";
//}auto it1 = arr.begin(); //迭代器
it1 = arr.insert(it1, 3); //向it1位置插入数据并修正it1,防止迭代器失效
for (auto a : arr)
{
cout << a << " ";
}
cout << endl;
arr.erase(it1); //删除it位置的数据,position就是这里的it,是一个迭代器
for(auto a : arr)
{
cout << a << " ";
}it1 = arr.begin();
auto it2 = arr.end();
auto it3 = find(it1, it2, 2); //在迭代器 it1 到 it2 的这段区间内查找“2”
cout << *it3 << endl;vector<int> arr1(3, 0);
swap(arr, arr1); //交换两个vector空间的数据
for (auto a : arr)
{
cout << a << " ";
}
cout << endl;for (auto a : arr1)
{
cout << a << " ";
}
return 0;
}
2.5 vector 迭代器失效问题(重点)
在上面的2.4中,有一行代码是这样写的:
it1 = arr.insert(it1, 3);
这个接口自己提供了返回值,那为什么要提供返回值?
如果不提供返回值,没有修正it1,此时的it1已经不是指向之前那个元素了,如果此时使用it1,很有可能造成错误,而且这个错误有可能很难发现,(gcc下,不报错,vs严格检查,此时会报错)
因此,修正迭代器是非常有必要的,这就涉及迭代器失效问题了
1.失效原因
会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、 assign、push_back等,为什么是有可能,原因之一是vector的自动扩容我们不知道什么时候会发生,以及上面所说的问题
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 给vector重新赋值,可能会引起底层容量改变
v.assign(100, 8);
/*
出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释
放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块
已经被释放的空间,而引起代码运行时崩溃。
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给
it重新赋值即可。
*/
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
2.解决方法
解决方法很简单,就是在使用前对迭代器重新赋值