vector是一个类模版,是一个顺序容器,底层思维就是顺序表,而顺序表的本质就是一个可以改变size的数组。本篇基于string的学习基础,我们对vector进行一个大致的了解和学习
1.基本介绍
- vector 是表示可变大小数组的序列容器,几乎所有类型都可以成为vector的元素,因此vector也可以进行嵌套,如:vector<vector<int>>
- 就像数组一样, vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
- 与其他动态容器的使用区别: 与其它动态序列容器相比( deque, list and forward_list ), vector 在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 list 和 forward_list 统一的迭代器和引用更好
- 时间和空间的使用:
vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 ,为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小。
vector 分配空间策略: vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
2.vector基础操作
2.1构造函数
正如上文所说,vector是类模版,类模版的本质是写给编译器用的,以实现泛型编程。
函数传对象,模版传类型,后者是在编译的时候传的,编译器会根据你传入的类型通过模版自动生成一个相应的vector
![](https://img-blog.csdnimg.cn/direct/ef60a566bbba4949b36ccee02073df4f.png)
vector的数组是所有类型的数组。
对于string和vector<char>的区别,前者是基于"串"而存在的概念,后者是基于"单个字符"而存在的概念。所以vector没有append和+=的概念。
string是专门针对char的数组设计的,vector是针对所有类型的数组设计的。
![](https://img-blog.csdnimg.cn/direct/5744a12193cf463ea9cb9e3da9100d19.png)
![](https://img-blog.csdnimg.cn/direct/a8d7bc9ecce44154997ec8650dc59771.png)
无参构造和拷贝构造是重点。也可以通过中括号里加参数给vector赋值。也就是:
vector<char>{'a','b'};
对于第一个无参构造,第一个参数(value_type)类型对应底层是一个什么类型的数组,第二个参数(allocator_type)有缺省值,allocator是空间配置器,也就是内存池,通过该空间配置器会给vector开空间。
对于第二个赋值构造,我们也可以使用如下:
(最后两排是其他用法)
对于参数的缺省值value_type()和allocator_type(),就像int()一样,表示创建一个匿名对象。在C++中有默认的构造函数的,int()就是0,char()就是\0,而如果是自定义类型就会调用默认构造。
![](https://img-blog.csdnimg.cn/direct/9bdb1f3d2ce249988137c25bc52d224f.png)
2.2 遍历vector
如何遍历?正如上文,vector不仅支持基于迭代器的范围for,还可以下标访问。
vector支持下标访问:
![](https://img-blog.csdnimg.cn/direct/7ffb9be6e51e4dffba57f6297853efd5.png)
![](https://img-blog.csdnimg.cn/direct/443ed7a02c954bd599f2693cd15d756a.png)
访问方法2:
迭代器:
cpp
vector<int>::iterator it1 = v.begin();
while (it1 != v.end()) {
cout << *it1 << " ";
++it1;
}
++虽然vector还是更多喜欢下标访问,但是迭代器可以便于实现范围for和提高接口的通用性。++
2.3 push_back和pop_back的使用
我们以一个string为元素的vector来举例(类似于一个二维char数组)
string类型的vector**就可以用到匿名对象(第二个push_back)**来便于创建:
也可以直接传常量字符串来构造:
![](https://img-blog.csdnimg.cn/direct/f0acdaff28dc425a941415595c69c3fc.png)
我们简单来看下push_back的参数。因为对于push_back:
![](https://img-blog.csdnimg.cn/direct/35e3e102a51d4771828182a8facb54fb.png)
push_back的参数必须是const string& s,因为涉及传常量字符串和单参数构造函数隐式类型转换,需要改小push_back处参数的权限。
当string作为vector的元素时,就可以对vector的元素进行+= , 因为strng是可以+=的。
pop_back:
![](https://img-blog.csdnimg.cn/direct/0a7243bf57954ffe964b5643e3cbc0af.png)
2.4对于需要深拷贝的传值优化
不过此时遍历的范围for写的有缺陷,因为e是传值传参,所以每个string都需要深拷贝。但是如果底层的拷贝用的是写时拷贝,在效率方面就优化了太多。
但是当我们不知道底层是不是写时拷贝时,就可以用以下的传引用方法,来降低拷贝的成本。
cpp
for (const auto& e : v1) {
cout << e << " ";
}
2.5 insert和erase等
![](https://img-blog.csdnimg.cn/direct/e85134656a794bd9955917cfbd475bde.png)
在vector中,insert和erase都没有下标版本,只有迭代器版本:
可发现不能传位置了。
而应该写成 :
![](https://img-blog.csdnimg.cn/direct/6cd9f6de248145029a0f0e0947434aa3.png)
![](https://img-blog.csdnimg.cn/direct/f299d3ed59fe4853bed80710a26919f5.png)
再来观察一下两种insert的用法:
![](https://img-blog.csdnimg.cn/direct/a2aeb22622874bc69efb8eb532625286.png)
另一种则可以控制多少个数据需要被修改成最后的参数:
![](https://img-blog.csdnimg.cn/direct/46e430a57afb47b8b6be0152330bc699.png)
emplace和insert的作用是几乎一样的,但是效率有点小差距。
![](https://img-blog.csdnimg.cn/direct/1dede14b466f4257bcfa86976f084fdc.png)
2.6 resize
resize不会改变原有空间的数据,而是在多开出的空间输入你的参数。相当于在数组末尾insert。
![](https://img-blog.csdnimg.cn/direct/16f3e888ff5546918421c7db0e3f6698.png)
![](https://img-blog.csdnimg.cn/direct/7293cc352b9741689690cc658b3177b3.png)
vector的resize不像string那样默认加\0,而是加0:
![](https://img-blog.csdnimg.cn/direct/ef87fd9181fe4ce392d3d0e0c09984d7.png)
2.7 sort的使用
sort是一个函数模版,参数为迭代器。
![](https://img-blog.csdnimg.cn/direct/8bcf5d0d4e28433e91ee3351358f8b2b.png)
![](https://img-blog.csdnimg.cn/direct/d07568ca773c4a2a88a9780a70f97a3c.png)
可以通过v1.begin()+-n来控制从哪开始,或者利用size来只排序前一半:
![](https://img-blog.csdnimg.cn/direct/86d2d2aa3c21459d82266a6b87205338.png)
默认排序是升序,如果希望降序:
![](https://img-blog.csdnimg.cn/direct/a8ad3c229e704cd7bc4c758c6831b77c.png)
sort的第三个参数表示需要传一个可比较的对象(就像qsort需要传一个函数指针一样)
使用仿函数,仿函数是一个对象
![](https://img-blog.csdnimg.cn/direct/a1f0643c66c045cbb02294cf58bed6cb.png)
greater是在库中实现的,可以选出更大的,相应的还有less:
![](https://img-blog.csdnimg.cn/direct/99805ce71397495da98bcb4d72b197a7.png)
可以用greater或less比较两个数,输出为0或1。
![](https://img-blog.csdnimg.cn/direct/c5cc7f5ba6dd4ed2b1c4b3fed9a7c282.png)
或者也可以利用匿名对象:
![](https://img-blog.csdnimg.cn/direct/1d4e3faf1653442e82076c8e7833e1fb.png)
仿函数的具体内容会在后文中讲解,现在知道有这个东西就行了。
3.练习使用vector
从题目中也可以看出,C++一般都是传vector,而C语言是传int* 和为了能修改而不得不传指针的returnsize:
![](https://img-blog.csdnimg.cn/direct/9e0e0c2848604391b355804b7941cf42.png)
思路非常简单并且做过无数遍,按位异或即可。
![](https://img-blog.csdnimg.cn/direct/b53afce9b7754fda9d2e1f83c6eda113.png)
cpp
int singleNumber(vector<int>& nums) {
int ans=0;
for(auto e:nums){
ans^=e;
}
return ans;
}
重点理解:resize / operator[]以及vector的包含。
类似于一个二维数组
将vector<int>作为参数形成一个新的vector。
所以这里会实例化两个类,一个是vector<int>,另一个是vector<vector<int>>,而在杨辉三角中,每一个vector<int>的大小和元素都是不一样,如下图:
![](https://img-blog.csdnimg.cn/direct/2c5ec899188140ca8c60b804b892c332.png)
通过vector实现了动态二维数组,该数组可以直接由运算符重载而直接用两个方括号访问:
![](https://img-blog.csdnimg.cn/direct/0beafd73f6c64c919b15bf2c2520b8fb.png)
![](https://img-blog.csdnimg.cn/direct/767b965e4c5544ba9d3ccc3fa5f31146.png)
杨辉三角的解题思路对于现在的大家应该很简单了:
![](https://img-blog.csdnimg.cn/direct/4fd096103b4b4c059be2628b0c081709.png)
cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> v;
v.resize(numRows);
for(int i =0;i<numRows;++i){
v[i].resize(i+1,0);
v[i][0]=v[i][i]=1;
}
for(int i=0;i<numRows;++i){
for(int j=0;j<=i;++j){
if(v[i][j]==0){
v[i][j]= v[i-1][j]+ v[i-1][j-1];
}
}
}
return v;
}
};
Leetcode传一维数组时需要一个returnsize,为了能改变其值所以传进来的是int* returnsize(正如第一题)
传二维数组时还需要一个数组returncolumnsize来记录每一个一维数组有多少个元素,为了能改变这个一维数组的值只能传int** returncolumnsize.