目录
前言:
本篇文档图片引用自:https://cplusplus.com/reference/vector/vector/
1.vector的结构
2.迭代器类型
3.构造函数
4.迭代器
反向迭代器遍历
const迭代器
5.容量
maxsize
shrink_to_fit
reverse
resize
6.修改
insert和erase
7.vector的3种常见遍历
总结:
前言:
本篇来认识与简单使用一下容器vector,vector算是使用比较频繁的容器了,这里只介绍常用的接口。还会介绍一下像迭代器的类型的一些内容,方便为下面实现与其它容器打基础。
本篇文档图片引用自:https://cplusplus.com/reference/vector/vector/
1.vector的结构
vector是通过动态的顺序表,动态也就是所谓的可以扩容,而顺序表也不陌生,就是数组。
vector支持对任意类型的储存,当然vector<vector<int>>这样的也不见怪。
另外vector中的第二个模版参数为一个空间配置器, 是为了申请空间时不必一直像系统申请内存,所以还有一个内存池的概念,这里暂时不再展开。
2.迭代器类型
迭代器分为三种类型:单向迭代器,双向迭代器,随机迭代器。
forward_list单链表,unordered map类型的迭代器就是单向迭代器,只能++,不能--,所以也就没有rbegin这样的反向迭代器。
list类型的迭代器就是双向迭代器,可以++,--,但不能+,-。
vector和string类型的迭代器就是随机迭代器了,可以++,--,+,-。这一点在下面会展示。
int main(){vector<int> v1;vector<int> v2(v1.begin()+5, v1.end()--);//随机迭代器,使用正确forward_list<int> f1;forward_list<int> f2(f1.begin(),f1.end()--);//单向迭代器,报错forward_list<int> f3(f1.begin()+2,f1.end());//单向迭代器,报错list<int> l1;list<int> l2(l1.begin(),l1.end()--);//双向迭代器,使用正确list<int> l3(l1.begin()+2,l1.end());//双向迭代器,报错return 0;}
3.构造函数
(1)是无参的构造,而里面的参数是代表可以构造的时候自己传一个自己写的空间配置器,暂时不用管,默认为无参的。
(2)是带参的构造,先传一个要初始化的个数,再传初始化的值。
vector<int> v2(10, 1);for (auto e : v2){cout << e << endl; }
(3)是传迭代器区间,因为它是一个模版所以现在传什么类型的迭代器都可以。而这里的InputIterator,查阅参数类型的解释,它是一个:
这里的input iterator的迭代器类型就支持我们刚刚说的三种迭代器类型,分别是单向,双向,与随机迭代器。所以说,当我们往参数传迭代器类型的时候,就会判断这个迭代器的类型,进而限制使用一些操作。
vector<int> v2(10, 1);for (auto e : v2){cout << e << endl;}vector<int> v3(v2.begin(), v2.end());for (auto e : v3){cout << e << endl;}
注意,所有的迭代器区间的范围都是左开右闭。
string s1("hello world");vector<char> v4(s1.begin(), s1.end());for (auto e : v4){cout << e << endl;}
string迭代器区间进行构造 vector对象。
(4)是拷贝构造。
4.迭代器
迭代器上面已经介绍的差不多了,迭代器是容器通用的,它是主力军,因为对于string和vector遍历使用内部重载的[]比较方便,但到了后面使用list再使用[]便利时间复杂度就高了。(因为链表的排列不是顺序的,找到对应的元素就需要遍历一遍,而使用迭代器就不会有这样的烦恼了。)
反向迭代器遍历:
vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//vector<int>::reverse_iterator rit = v1.begin(); //报错//auto rit = v1.rbegin(); //可以这样写,但是可读性不高vector<int>::reverse_iterator rit = v1.rbegin();while (rit != v1.rend()){cout << *rit << " ";++rit;}cout << endl;
注意不使用配套的迭代器会报错,例如使用迭代器接收反向迭代器,类型不匹配。
迭代器类型可以使用auto来根据后面的内容进行推导,但是可读性不高。
const迭代器:
只读不能写,可以使用begin(),也可以使用c++11的cbegin()。
5.容量
maxsize:
最大40多亿字节。也有可能是10多亿对象就是除以整型的4字节,相当于有10多亿的对象,用的不多,不同平台实现不一样,现实意义不大。
shrink_to_fit:
少用,就算原本有capacity为100,resize(10)也不会缩容,只会改变size,用shrink_to_fit会缩,但是大部分都不是原地缩容(开空间的函数例如malloc都没有说原地缩容的)。异地缩容代价很大,需要开一块空间,再进行拷贝。
reverse:
空间容量不够,自动扩容,不同的编译器自动扩容的大小不一样。
打印日志观察自动扩容:
可以看到vs下大概是1.5倍扩容的,而linux下是大概2倍扩容的。
reverse的作用就是:一方面如果提前知道有多大的空间使用,使用reverse提前开好空间,就不会让编译器自动扩容而可能造成空间的浪费;另一方面reverse提前一步扩好容,开好空间,肯定比编译器自动的满了扩一次,满了再扩一次的效率上有一定的提升。
resize:
就是开空间加初始化。
6.修改
insert和erase:
由于vector没有查找的接口,所以推荐使用库中的查找函数find,同样也可以供所有容器使用:
注意模版中的first和last参数,文档中有详细的解释,如果找到了,就返回第一个与查找值相同的位置的迭代器;如果没有找到,就返回迭代器的右开区间:
vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;vector<int>::iterator pos = find(v1.begin(), v1.end(), 1);if (pos != v1.end()){v1.insert(pos, 20);}for (auto e : v1){cout << e << " ";}cout << endl;
上面代码中查找到的1也就是pos的位置可以理解为位于下标0的位置,所以返回的迭代器是指向第一个位置的, 所以再在pos这个迭代器位置插入数据就是在第一个位置插入数据也就是在开始插入一个20:
对于erase而言,不能直接就删除,还需要再查找一遍,不然会面临迭代器失效的问题,这里在后面的底层实现中会详细分析:
正确做法:
pos = find(v1.begin(), v1.end(), 2);if (pos != v1.end()){v1.erase(pos);}for (auto e : v1) {cout << e << " ";}cout << endl;
但是对于头尾的数据是可以直接删除的,至于为什么,后面再说:
v1.erase(v1.begin());v1.erase(v1.end() - 1);for (auto e : v1) {cout << e << " ";}cout << endl;
7.vector的3种常见遍历
vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (size_t i = 0; i < v1.size(); ++i){cout << v1[i]<< " ";}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it++<< " ";//++it;}cout << endl;
注意范围for的缺点就是不能倒着遍历。
总结:
本篇主要需要掌握vector的基本使用例如使用迭代器遍历,下标遍历,范围for遍历,还要掌握迭代器的类型,重要的是迭代器的插入与修改,引出的迭代器失效问题会在vector的底层模拟实现中涉及。