vector的介绍和使用
一.vector的文档介绍
-
vector是表示可变大小数组的序列容器。
-
就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素
进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自
动处理。 -
本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小
为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是
一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大
小。 -
vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存
储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是
对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。 -
因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增
长。 -
与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在
末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和
forward_lists统一的迭代器和引用更好。
简单来说,vector就类似于我们数据结构中的线性表,它可以动态增容。
二.vector的使用
1.vector对象的创建
创建vector对象的模板:vector<类型名> 对象,其中的类型名可以是任意类型,如int,char,double,string等等。
如下:
void vectortest()
{
vector<int> v1(3, 3);//创建一个v1的对象,里面存储的是int类型的数据
vector<char>v2(3, 'a');//创建一个v1的对象,里面存储的是char类型的数据
vector<string>v3(3, "张三");//创建一个v1的对象,里面存储的是string类型(字符串)的数据
}
通过调试,可以看出v1存储3个int类型的数据,v2存储3个char类型的数据,v3存储3个string类型的数据。
构造函数声明 | 接口说明 |
---|---|
vector() | 无参的构造函数 |
vector(const vector v) | 拷贝构造 |
vector (InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
void vectortest1()
{
vector<int> v1;//无参构造
vector<int> v2(10, 3);//构造并初始化10个3
vector<int>v3(v2);//拷贝构造
vector<int>v4(v2.begin(), v2.end());//利用迭代器将v2中的数据构造出v4
}
2.迭代器 iterator的使用
项目 | 说明 |
---|---|
begin()+end() | begin()返回的是vector对象的第一个数据地址,end()返回的是最后一个数据的下一位置的地址 |
rbegin()+rend() | rbegin()获取的是vector对象的最后一个数据的地址,rend()获取的是vector对象第一个数据的前一个位置的地址。 |
3.vector空间
接口 | 接口说明 |
---|---|
empty | 判断是否为空 |
size | 获取数据个数 |
capacity | 获取容量的空间大小 |
resize | 改变size的大小 |
reserve | 改变容量的大小 |
void vectortest()
{
vector<int> v1(3, 3);
cout << v1.size() << endl;//获取v1中的数据个数为3
cout << v1.capacity() << endl;//获取v1中的容量为3
v1.resize(10);//将v1的size从3变为10,同时capacity也会从10变为20
v1.
cout << v1.size() << endl;//获取v1中的数据个数为10
cout << v1.capacity() << endl;//获取v1中的容量为10
v1.reserve(20);//将capacity从10变为20,size不变
cout << v1.size() << endl;//获取v1中的数据个数为10
cout << v1.capacity() << endl;//获取v1中的容量为20
}
补充:
1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。c++标准只是定义vector和它的接口的功能,但每个编译器底下实现这些接口函数的可能会有所差异,所以一段相同的代码在vs编译器可能运行得起来,在linux下可能运行不起来,也可能某些数运行出来有所差异。
2.reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
4.vector的增删查改
接口 | 接口说明 |
---|---|
push_back | 尾插数据 |
pop_back | 尾删数据 |
insert | 在position之前插入一个数据 |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] | 像数组一样去访问vector的数据 |
find | 查找,这个不是vector上的接口函数,而是在算法里面的.。 |
注明:任何容器都可以使用find,只要传迭代器给它,它就可以在这个迭代区间进行查找某个数据,找到了就返回地址,如果找不到,就返回end()。
输出的结果:
void vectortest4()
{
vector<int> v1(3, 1);//创建v1,并初始化3个1
vector<int> v2(4, 2);//创建v2,并初始化4个2
v1.swap(v2);//将v1和v2中的数据进行交换,包括capacity和size
}
三.迭代器失效的问题
迭代器(iterator)是一个可以对其执行类似指针的操作(如:解除引用(operator*())和递增(operator++()))的对象,我们可以将它理解成为一个指针。
什么是迭代器失效呢?我们先来看一个例子:
void vectortest4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);//v1中尾插4个数据
vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
v1.insert(pos, 0);//在3的位置之前插入一个0,此时pos位置指向的数据就变为0,此时迭代器失效
}
1.第一种情况:我们通过迭代器找到3的位置pos,然后在pos位置插入0,插入0之后,pos指向的数据就由3变成0了,迭代器就失效了。我们再去用这个迭代器pos,有的编译器就会报错。
2.第二种情况:我们通过迭代器找到3的位置pos,然后将3给删掉,这时迭代器pos指向的数据就变为4,同样,迭代器也会失效。
3.第三种情况:我们通过迭代器找到3的位置pos,然后在通过pos在3之前插入一个0,但是v1容量已经最大,此时就需要增容,就先开辟一块更大的空间,把久空间的数据拷贝给新空间,再把久空间给处理掉,所以pos就变为野指针(在vector底下层,迭代器就是指针)。这时候迭代器也失效。不能再用它
总结:1.如果我们利用迭代器将在某个数据之前插入一个数据之后,或者利用迭代器删除某个数据之后,迭代器指向内容就会改变,也就是迭代器的意义改变,迭代器就失效,就有可能引发错误。
2.如果想插入某个数据,出现增容的情况,pos指向的空间已经释放,此时的pos就变成野指针。迭代器也失效
所以:迭代器失效的解决办法是:在使用之前,对迭代器重新赋值即可。
写一段代码,删除vector中的所以偶数
接下来我们在看一下下面这段代码是否正确:
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0; }
在vs测试了一下,发现这段代码会奔溃。为什么呢?
我们在删除数据时,后面的数据就会往前挪动,end()也会向前挪动一步,然后it在++,如果删除最后一个位置的数据,此时it和end()就不会相遇,最终程序奔溃。
所以我们对代码改动一下,如果删除时,it就不会走一步,没有删除,it才走一步
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
++it;
}
return 0;
}