之前我们学习了STL的字符串string类, 不知道大家用起来方不方便, 反正我是用起来非常奈斯的~.
这次我们来学习另外一个非常非常重要的容器 — vector — 可变的数组容器, vector使用起来也是十分的方便, 解决了之前要考虑数组长度, 空间够不够用的问题…那么话不多说, 直接开始吧. 耐心看完 你一定有所收获~
一. vector的认识
吹了半天, 那么vector到底是什么呢.?
下面进行简单介绍:
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
- 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。
二. vector常用接口说明
vector实际上是一个泛型类, 为了容纳不同类型的数据, 所以使用时必须显式实例化指定要装的数据类型
1. vector的构造函数
Construct | 接口说明 |
---|---|
vector() | 构造一个空的vector |
vector (n , val) | 用n个数据val构造一个vector |
vector (const vector& x) | 拷贝构造 |
vector (InputIterator first, InputIterator last) | 用两个迭代器表示的区间构造 |
这里顺便说一下赋值:
vector& operator= (const vector& x)
用一个vector对象给另一个已经存在的vector对象赋值, 是深拷贝
下面给出测试代码:
void test() {
//vector构造
vector<int> v1;
vector<char> v2;
vector<int> v3(10, 1); //10个1
vector<int> copy(v3); //拷贝构造
int arr[] = { 1, 2, 3, 4, 5 };
vector<int> v4(arr, arr + 5);
//赋值: 深拷贝
v1 = v4;
//拷贝构造
vector<int> v5 = v4;
}
调试结果如下:
2. 迭代器和访问
和string的迭代器使用方法一样, STL的容器保证了迭代器的使用方法一致, 降低了我们的使用成本
Iterators | 接口说明 |
---|---|
begin | 指向第一个元素的位置 |
end | 指向最后一个元素的下一个位置 |
rbegin | 反向迭代器, 指向最后一个元素 |
rend | 反向迭代器, 指向第一个元素前一个位置 |
cbegin | |
cend | |
crbegin | 下面这四个都是const迭代器, 与上面对应, 无法修改内容 |
crend | |
front | 获取第一个元素 |
back | 获取最后一个元素 |
front和back 的返回值都是引用类型, 所以可以直接修改vector的内容
接下来我们来遍历上面构造的vector
struct A {
int _a = 1105;
};
void test() {
//vector构造
vector<int> v1;
vector<char> v2;
vector<int> v3(10, 1); //10个1
vector<int> copy(v3); //拷贝构造
int arr[] = { 1, 2, 3, 4, 5 };
vector<int> v4(arr, arr + 5);
//赋值: 深拷贝
v1 = v4;
//拷贝构造
vector<int> v5 = v4;
//迭代器
vector<int>::iterator it = v4.begin();
while (it != v4.end()) {
cout << *it << " ";
it++;
}
cout << endl;
vector<char>::iterator it2 = v2.begin();
while (it2 != v2.end()) {
cout << *it2 << " ";
it2++;
}
cout << endl;
//自定义类型
vector<A> v6(5, A());
vector<A>::iterator it3 = v6.begin();
while (it3 != v6.end()) {
cout << (*it3)._a << " ";
it3++;
}
cout << endl;
//反向迭代器
vector<int>::reverse_iterator rit = v4.rbegin();
while (rit != v4.rend()) {
cout << *rit << " ";
rit++;
}
cout << endl;
//范围for
for (const auto& val : v4) {
cout << val << " ";
}
cout << endl;
//operator[]
for (int i = 0; i < v4.size(); i++) {
cout << v4.operator[](i) << " ";
cout << v4[i] << " ";
}
cout << endl;
//operator[]越界: assert错误
//at() 越界: 异常
cout << v4[20] << endl;
cout << v4.at(20) << endl;
}
运行结果如下:
上述代码运行时会报错, 大可不必惊慌, 那是最后两行代码, 为了说明operator[] 和 at() 越界时的不同反应,
operator[ ] 越界: assert错误
at() 越界: 异常
3. 容量相关
Capacity | 接口说明 |
---|---|
size | 数组的有效字符长度 |
capacity | 当前数组能存放的最大字符个数(空间大小) |
empty | 检测字符串是否为空, 为空返回true, 否则返回false |
resize | 设置/修改有效元素个数size |
reserve | 设置/修改容量capacity的大小 |
shrink_to_fit | 把容量缩小到合适 |
resize(n): n > size 在[size, n) 赋默认值:0,’\0’,nullptr,自定义类型调默认构造
原则: 内置类型初始化为类型对应的0, 自定义类型调默认构造resize(n, val) : 一定影响size: size = n
n > capacity 增容
当 n > size时val才有效
下面给出测试代码:
void test2() {
vector<int> v(3, 1);
vector<char> v2(10, 'a');
vector<A> v3(5, A());
cout << "v size: " << v.size() << endl;
cout << "v2 size: " << v2.size() << endl;
cout << "v3 size: " << v3.size() << endl;
cout << "v max size: " << v.max_size() << endl;
cout << "v2 max size: " << v2.max_size() << endl;
cout << "v3 max size: " << v3.max_size() << endl;
vector<int> v4;
vector<char> v5;
cout << v4.capacity() << endl;
cout << v5.capacity() << endl;
v4.push_back(1);
cout << v4.capacity() << endl;
v4.push_back(2);
cout << v4.capacity() << endl;
}
运行结果如下:
resize的测试
void test3() {
vector<int> v;
v.resize(5);
vector<char> v2;
v2.resize(5);
vector<int*> v3;
v3.resize(5);
vector<A> v4;
v4.resize(5);
vector<char> v5(10, 'a');
v5.resize(20, 'b');
v5.resize(21, 'c');
v5.resize(5, 'd');
}
调试结果如下:
v.resize(5):
v2.resize(5):
v3.resize(5):
v4.resize(5): 自定义类型, 调默认构造
v5(10, 'a') : 构造v5
v5.resize(20, 'b') : 在10 - 19 补了'b'
v5.resize(21, 'c') : 在最后补了一个c
v5.resize(5, 'd') : 从第五个元素截断, 给的'd'值没有用
reserve: 只改变capacity, 不改变size
vector的 reserve 只会增容, 不会缩容
接下来我们看看VS中的vector的增容方式:
void printCap() {
vector<int> v;
//vector增容: PJ版本: 1.5倍 SGI: 2倍
size_t cap = v.capacity();
cout << "v capacity: " << v.capacity() << endl;
for (int i = 0; i < 150; i++) {
v.push_back(i);
if (cap != v.capacity()) {
cap = v.capacity();
cout << v.capacity() << endl;
}
}
}
运行结果如下:
我们可以看到容量大致是以1.5倍增容的
reserve的测试:
void test4() {
vector<int> v;
cout << "cap: " << v.capacity() << endl;
//reserve: 不会影响size
v.reserve(100);
cout << "cap: " << v.capacity() << endl;
//vector的reserve只能增容, 不会减小容量
v.reserve(10);
cout << "cap: " << v.capacity() << endl;
v.resize(35);
//减小容量, 用多少剩多少
v.shrink_to_fit();
cout << "cap: " << v.capacity() << endl;
}
运行结果如下:
经过大量测试, 我发现shrink_to_fit()会把capacity缩减至有效元素的个数
4. 内容修改相关
Modifiers | 接口说明 |
---|---|
push_back | 尾插一个元素 |
pop_back | 尾删一个元素 |
insert | 在pos位置前插入数据 |
earse | 删除pos位置数据 |
assign | 赋值 |
swap | 交换两个vector的数据空间 |
clear | 清空数组 |
要注意的是: vector的 insert 和 erase 都是要用迭代器指定pos位置
插入删除的各种接口用以下代码说明: (结果都写在注释中)
void test5() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.pop_back();
v.pop_back();
v.pop_back();
//insert() 位置必须由迭代器指定
v = { 1, 2, 3 , 4 };
v.insert(v.end(), 5); // 1 2 3 4 5
v.insert(v.begin(), 0); // 0 1 2 3 4 5
v.insert(v.begin() + 2, 10); // 0 1 10 2 3 4 5
v.insert(v.end() - 1, 20); // 0 1 10 2 3 4 20 5
v.insert(v.begin(), 3, -1); //头插3个-1
vector<int> v2(3, 9);
//头插一个v2的区间[begin, end)
v.insert(v.begin(), v2.begin(), v2.end());// 9 9 9 ~
//迭代器区间: 左闭右开
//迭代器类型: 不一定是vector迭代器,
//只要迭代器解引用后类型和vector存放类型一致即可 (数组是一个天然的迭代器)
int arr[] = { 5, 5, 5 };
v.insert(v.end(), arr, arr + 2); // ~ 5 5
//erase 删除
v = { 0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
v.erase(v.begin()); //头删 1 ~ 9
v.erase(v.end() - 1); // 尾删 end位置没有元素 1 ~ 8
v.erase(v.begin() + 1, v.end() - 1);// 只剩头尾元素 1 8
}
三. 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装
因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。
- 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、 push_back等
比如:
//resize
v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
it = v.begin();
v.resize(5);
cout << *it << endl; //减小容量, 没问题
v.resize(9);
cout << *it << endl; // 没超过cap, 不增容, 没问题
v.resize(10);
cout << *it << endl; //增容, 迭代器失效
// reserve
v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
it = v.begin();
v.reserve(5);
cout << *it << endl; //不会减小容量, 没问题
v.reserve(50);
cout << *it << endl; // 增容, 失效
2. 插入删除引起迭代器失效
//1. 插入导致
for (int i = 0; i < 10; i++) {
v.push_back(i);
cout << *it << " "; //插入之后可能会增容, 导致失效
}
cout << endl;
//2.
v.clear();
it = v.begin();
v.insert(it, 0); //插入没问题
cout << *it << endl; //这里空间已经没了..
// 3. 删除导致
v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
it = v.end() - 1; // 指向9
v.erase(it);
cout << *it << endl; //位置越界
// 4.
v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
it = v.begin(); // 指向1
v.erase(it);
cout << *it << endl; //位置错位, 也不允许访问 (PJ版)
迭代器失效的问题很严重, 而解决的方法很简单 : 重新获取的迭代器即可
所以大家在使用迭代器涉及到插入删除等引起空间变化的操作时, 尽量要重新获取一下迭代器, 防止出现越界或者野指针的问题, 导致程序崩溃
vector的接口就说到这里了~