我们在前面已经学习了string,其实string就是一个元素为字符的vector,所以在我们已经学习了string的基础上,再学习vector是很轻松的。
vector的介绍
1.vector是表示可变大小数组的序列容器
2.vector和数组一样,也是采用连续空间来存储数组,也就是说可以用下标的方式对vector的元素进行访问
3.vector的大小可以动态变化,当vector需要重新分配大小时,会开辟一个新数组,然后将所有元素移到当前这个新数组中,并释放原来的数组空间
4.vector分配空间的策略:vector会分配一些额外的空间适应可能的增长,因此存储空间一般会比实际需要的空间更大一些,所以在末尾插入元素时是在常数的时间复杂度内完成的。
5.因为vector采用的是连续空间来存取元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,尾插尾删操作相对也更加高效,而对于在中间或头部进行插入删除操作的效率就会相对较低。
vector的使用
vector的定义
vector有多种构造方式,下面我们通过代码来分别介绍:
vector<int> v;//构造一个名为v的空容器
vector<int> v1(10,2);//构造一个含有10个数值为2的int型容器
vector<int> v2(v1);//拷贝构造一个v1的int型容器
vector<int> v3(v2.begin(),v2.end());//使用迭代器拷贝构造v2容器的一部分内容
//使用迭代器可以拷贝其他容器的某一段内容
string s("hello");
vector<char> v4(s.begin(),s.end());//这里使用迭代器拷贝string对象中的某一段内容
我们来解释一下,既然vector的元素也可以是char类型,为什么还要string呢?
因为虽然他们底层都是数组中存储char,但还是有不同之处的,相较于T为char的string,string对象指向的空间结尾有'\0',这样就符合C字符串的规范,例如我们可以实现strstr,strcpy等函数,但是vector就不能实现+/-=字符串等的操作。所以vector<char>依然不能替代string。
vector的迭代器使用
begin && end
获取第一个位置的iterator && 获取最后一个数据的下一个位置的iterator
rbegin && rend
获取最后一个数据位置的reserve_iterator && 获取第一个数据的前一个位置的reserve_iterator。
还有const迭代器,使用方法完全跟string类似,这里就不详细介绍了。
我们同学可能会有疑问,例如下面的代码,为什么++rit会倒着走呢?这是因为这里的rit已经不再是原生指针了,他是一个被封装的类对象,重载operator++,才能实现++rit是反着走。
vector<int>::reverse_iterator rit = v.rbegin();
while(rit != v.rend())
{
cout << *rit << " ";
++rit;
}
vector的空间增长问题
前面几个接口都是老生常谈的接口了,我们看看最后一个接口shrink_to_fit:
通过文档,我们可以知道,他的目的是减小vector对象的capacity为size,这样做可以减少空间浪费,但是最好是不在增长的vector对象,否则增容带来的效率损失很大。
vector增删查改
我们要注意因为vector底层实际是一个数组,所以头插头删的效率很低,因此没有push_front,pop_front这样在头上操作的接口,而剩下的也都是很熟悉的接口了,我们从头到尾捋一遍:
vector增删查改 | 接口说明 |
push_back | 尾插 |
pop_back | 尾删 |
find | 查找(这个是算法模块的实现,不是vector的成员接口) |
insert | 在pos位置插入值为val的元素 |
erase | 删除pos位置的数据 |
swap | 交换两个vector的数据空间 |
operator[ ] | 像数组一样随机访问 |
1.assign
assign的作用是将新的元素放入vector对象中,替换掉当前内容,并且更新该对象的size。
// vector assign
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> first;
std::vector<int> second;
std::vector<int> third;
//将first内容改为7个100
first.assign (7,100); // 7 ints with a value of 100
std::vector<int>::iterator it;
it=first.begin()+1;
//除了first的首尾元素,将剩下的5个元素赋值给second对象
second.assign (it,first.end()-1); // the 5 central values of first
int myints[] = {1776,7,4};
third.assign (myints,myints+3); // assigning from array.
//这里是用数组赋值给third,注意这里第二个参数一定是要加三,因为迭代器是左闭右开的,
//所以右面一定要多加一个
std::cout << "Size of first: " << int (first.size()) << '\n';
std::cout << "Size of second: " << int (second.size()) << '\n';
std::cout << "Size of third: " << int (third.size()) << '\n';
return 0;
}
我们这里一定要注意迭代器都是左闭右开的,所以很多地方第二个参数都要多加一个。
2.find && sort
vector并没有提供find,但是算法里提供了函数模板的find函数,之所以算法里提供find的原因是不仅你vector要使用find函数,list也要使用find函数,所以这里的find提供一个模板就解决了,你可以是vector的迭代器,也可以是list的迭代器。但可以是string的迭代器吗?不可以,或者说没必要,因为string提供了自己的find函数,为什么还要使用这个呢?而且string不仅要支持find一个字符,还要find一个字符串。(使用find的时候需要包含algorithm头文件)
使用find的格式:
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
这里的sort函数也是如此,是算法中的。
int a[] = { 1, 20, 10, 8, 7, 5 };
vector<int> v(a, a + 6);
//升序
sort(v.begin(), v.end());
//降序,这里需要传一个比较器对象,这里就涉及仿函数,后面我们会详细介绍,使用它需要包functional
/*greater<int> gt;
sort(v.begin(), v.end(), gt);*/
sort(v.begin(), v.end(), greater<int>());//同上,更推荐使用匿名对象
//sort不仅可以对容器排序,还可以对数组排序,因为指向数组空间的指针是天然的迭代器
int b[] = { 3, 4, 8, 2, 7 };
sort(b, b + 5);
}
vector迭代器失效
迭代器的作用就是为了能够让算法不关心底层的数据结构,采取统一的方法使用,其本质就是一个指针,或是对指针进行的封装。vector的迭代器就是原生指针T*。所以迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了(其实就类似于野指针问题),使用一块被释放的空间(即使用已经失效的迭代器)就会导致程序崩溃。
下面我们来逐个分析错误:
1:
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin();
v1.reserve(5);//对v1进行扩容,v1此时指向的空间不再是原来的空间了,所以再访问it指向的内容就会报错
while (it != v1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
结果为:
后面的例子也都会造成这种结果
2:
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
if(pos != v1.end())
{
v1.insert(pos, 20); //这里也是insert会导致增容,造成类似野指针的问题
}
cout<<*pos<<endl;
return 0;
}
3.
int main()
{
vector<int> v;
for (size_t i = 1; i <= 8; i++)
{
v.push_back(i);
}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0) //删除容器当中的全部偶数
{
v.erase(it);
}
it++;
}
return 0;
}
这段代码我们原意是要删除所有偶数的,但是我们仔细琢磨,就会发现其实代码会访问到不属于容器的内存空间,进而导致程序崩溃,并且在迭代器遍历容器的元素的时候,对所以奇数也都没有进行判断。
为了解决这个问题,我们可以用一个迭代器接收erase()函数的返回值,erase返回的是删除元素的下一个元素的迭代器:
int main()
{
vector<int> v;
for (size_t i = 1; i <= 8; i++)
{
v.push_back(i);
}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0) //删除容器当中的全部偶数
{
it = v.erase(it); //删除后获取下一个元素的迭代器
}
else
{
it++; //是奇数则it++
}
}
return 0;
}
我们解决迭代器失效的最好办法就是:每次使用前,对迭代器进行重新赋值。