1.Vector简介
(1)vector是表示可变大小数组的序列容器,也就是一个变长数组,是C语言数组的plus加强版。
(2)就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
(3)本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因此每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小,其分配空间策略见第四点。
(4)vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。类似于数据结构中当存储结构内存不足时申请空间的操作。
(5)与其他容器相比,vector访问元素更高效,因为和数组一样可以直接用下标访问。
2.vector的使用
看文档是一种很重要的学习方法,以下是vector的文档链接:
http://www.cplusplus.com/reference/vector/vector/
2.1 vector的定义
以上是vector的几种定义方式,
2.2 vector iterator的使用
iterator是一种设计模式,用于遍历集合中的元素。vector iterator是指针的一种类型,用于在vector容器中迭代(遍历)元素,类似于使用指针访问数组的方式。
(1)begin() 和 end() 重点掌握
begin()用于获取第一个数据位置的iterator/const_iterator,end()用于获取最后一个数据的下一个位置的iterator/const_iterator
(2)rbegin()和rend()
也就是begin和end的倒置,rbegin()获取最后一个数据位置的reverse_iterator,rend()获取第一个数据前一个位置的 reverse_iterator
注意:这里的rbegin()在遍历时仍旧使用++的方式,后面会代码实现。
2.3 vector的空间增长问题
在简介中提到了vector是一个动态数组容器,既然是动态的,那么空间问题就是一个不可避免的问题。
size()用于获取数据个数,类似于C语言中的sizeof(arr)/sizeof(arr[0])的操作。
capacity()用于获取容量大小,但注意容量和数据个数的区别。
resize可以改变vector的size,reserve可以改变vector的capacity,如果是想要预留空间的话使用reverse,resize会直接改变元素个数,增加的部分会初始化为0
empty用于判断vector是否为空,指的是size是否为0,为空返回true,非空返回false
下图是代码实现:
2.4 vector的初始化和遍历
vector的几种构造函数,对应以下几种定义方式:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
void test_vector1()
{
vector<int> v1;//无参的构造
vector<int> v2(10, 0);//初始化v2,v2内有10个0
vector<int> v3(v2.begin(), v2.end());//通过迭代器来初始化,将v2的内容全部拷贝给v3
//也可以通过迭代器将其他容器的内容拷贝到vector中
string s("JINITAIMEI");
vector<int> v4(s.begin(), s.end());//发生了隐式类型转化,将char类型转化为int类型。
vector<int> v5(v4);//拷贝构造
}
int main()
{
test_vector1();
return 0;
}
打印结果如下(全部使用范围for访问,范围for相关知识C++11中基于范围的for-CSDN博客):
对于vector的遍历,在C++11后使用范围for是很方便的。如果不使用范围for,一般会使用下标遍历或者迭代器遍历的方法,同时vector也是支持下标访问的,也就是[ ],这是运算符重载。
在使用迭代器时,注意begin指向的是vector首个元素的位置,而end指向的是最后一个元素的后一个位置,所以while循环判定继续的标志应该是!=v.end();
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
void test_vector1()
{
vector<int> v1;//无参的构造
vector<int> v2(10, 0);//初始化v2,v2内有10个0
vector<int> v3(v2.begin(), v2.end());//通过迭代器来初始化,将v2的内容全部拷贝给v3
//也可以通过迭代器将其他容器的内容拷贝到vector中
string s("JINITAIMEI");
vector<int> v4(s.begin(), s.end());//发生了隐式类型转化,将char类型转化为int类型。
vector<int> v5(v4);//拷贝构造
cout << "v2: ";
for (size_t i = 0; i < v2.size(); i++)//使用成员函数size()获取vector内元素的数量然后遍历
{
cout << v2[i] << " ";
}
cout << endl;
cout << "v3: ";
//vector<int>::iterator it = v3.begin();//迭代器的类型是vector<int>::iterator,当然也可以用auto直接推出类型
auto it = v3.begin();
while(it!=v3.end())//使用迭代器遍历
{
cout << *it << " ";
it++;
}
cout << endl;
cout << "v4: ";
for (auto x : v4)
{
cout << x << " ";
}
cout << endl;
cout << "v5: ";
for (auto x : v5)
{
cout << x << " ";
}
cout << endl;
}
int main()
{
test_vector1();
return 0;
}
2.5 vector的增删查改
尾插、尾删
就是直接调用成员函数,很常用且用法简单。
find查找
注意find不是vector的成员接口,是算法库提供的通用的,使用时需要头文件#include<algorithm>,并且值得注意的是迭代器中传区间都是左闭右开。
如果找到了就返回迭代器(指针),如果没有找到,就返回最后一个元素的下一个地址,也就是v.end()
insert插入
上图是insert的调用,从上往下依次为:
(1)在pos位置前插入val----------- v . insert ( pos , val );
(2)在pos位置前插入n个val;----------- v. insert (pos , n , val );
(3)在pos位置前插入一段迭代器区间 ----------- v. insert ( pos , frist , last );
插入常常与find一起使用,用来获取pos的位置,下面是一个例子:
int main()
{
vector<int> v;
v.push_back(1);//尾插
v.push_back(2);
v.push_back(3);
v.push_back(4);
//通过迭代器确定查找范围,返回iterator指针
vector<int>::iterator it = find(v.begin(), v.end(), 3);
v.insert(it, 6);//在3前插入一个6
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
it = find(v.begin(), v.end(), 3);//此时3的位置改变了,需要重新获取
v.insert(it, 3, 6);//在3前插入3个6
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
vector<int> v1 = v;
it = find(v.begin(), v.end(), 3);
v.insert(it, v1.begin(), v1.begin() + 2);//在3前插入一段迭代器区间
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
return 0;
}
erase 删除
(1)删除pos位置的元素----------- v.erase( pos );
(2)删除一段迭代器空间------------v .erase( first , last );
swap 交换
在算法库里面也有一个通用的swap,但是通用swap会涉及深拷贝,会降低运行速度。
3. vector的动态二维数组
动态二维数组其实就是一个vector中的每一个元素都是一个vector<T>*,指向几个连续的,所以格式为
vector<vector<T>> T为类型
3.1初始化
vector动态二维数组的存储是比较特殊的,下面的链接讲述了存储方式,但如果只考虑到使用的话,可以这样理解:vector动态二维数组可以同理为一个指针数组,这个指针数组的每个元素又指向一个个的数组,这些数组在物理上是不连续的,由此构成了动态二维数组。当然这是不严谨的。
这里使用了resize对有多少行进行了设定。这是一定要进行的操作,就相当于C语言二维数组需要定义有多少列类似,其实也就是对需要多少个指向vector的指针做了规划,如果没有强行插入就会越界。但不同于C语言二维数组的是,它在物理上不是连续的,所以是可以随时使用resize改变其行数,如下所示:
3.2增删查改:
这些操作都是先确定在哪一行,再进行和一维一样的操作即可。
尾插、尾删
动态二维数组的尾插是在确定的一行进行插入,例如v[0].push_back(1)是在第0行尾插了一个1,因为push_back会自动扩容,所以不需要确定有多少列。
同理pop_back尾删也需要先确定在哪一行进行尾删,例如v[1].pop_back()是在第一行进行了尾删
find查找、iterator、insert、erase、size
看图即可