vector 包含在
在使用时需要包含头文件
#include
在cplusplus里面的vector的简介
vector是表示可以改变大小的数组的序列容器。
这里对vector还有很多的介绍解释,这里就不再一一的翻译了,对它主要的进行了解就可以了,
1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。
但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。
其做法是分配一个新的数组,然后将全部元素移到这个数组。一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。
不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deques, lists and forward_lists),vector在访问元素的时候更加高效,在 末尾添加和删除元素相对高效。
对于其它不在末尾的删除和插入操作,效率更低。比起lists和 forward_lists统一的迭代器和引用更好。
序列容器中的元素按严格的线性顺序排列。各个元素按其在此顺序中的位置进行访问。**
允许直接访问序列中的任何元素,甚至通过指针算法,并在序列末尾提供相对快速的元素添加/删除。
容器使用分配器对象动态处理其存储需求。
说了那么多大家可能还是不太理解,在c++ prime这本书里,介绍到:
vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。 和 string 对象一样,标准库将负责管理与存储元素相关的内存。我们把 vector 称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种 类型的。
vector 是一个类模板(class template)。使用模板可以编写一个类定义 或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存 string 对 象的 vector,或保存 int 值的 vector,又或是保存自定义的类类型对象(如 Sales_items 对象)的 vector
那么具体该如何定义呢?
构造函数声明 | 接口说明 |
---|---|
vector |
vector保存类型T的对象,默认构造函数v1为空 |
vector |
用v1拷贝构造v2 |
vector |
用n个 值为 i 的元素来构造v3 |
vector |
v4 用n个T类型的默认值来构造v4 |
vector |
使用迭代器来构造v5 |
具体的定义来看一下代码:
void Test1()
{
vector<int> v1;
vector<string> v2;
vector<double> v3;
vector<char> v4;
}
可以在这个容器中我们可以存储各种基本类型的数据,当然也可以存储自定义的数据类型
class A
{
};
void Test1()
{
vector<A> v5;
}
void Test2()
{
vector<int> v1;
v1.push_back(10);//尾插一个数据
vector<int> v2(v1);
cout << v2[0] << endl;//既然vector是一个相当于数组一样
//的容器,那么肯定也是可以使用下标进行访问的
}
注意 使用另一个vector拷贝构造另一个vector对象时,两者的类型必须相同否则无法完成拷贝构造
vector<int> v1(10, 1);
vector<string> v2(10,"hi");
vector<double> v3(10,2.34);
vector<char> v4(10,'c');
相比于 vector
之前我们已经学习过string 对迭代器有了一定的了解 ,vector同样也是可以使用迭代器,对vector对象进行一定的访问操作
先大概了解一下vector的迭代器构造方法:
void Test5()
{
vector<int> v1(10, 5);
vector<int> v2(v1.begin(), v1.end());
for (auto e : v2)
{
cout << e << " ";
}
}
这种迭代器构造有点像我们的拷贝构造,但又不仅仅这些,在讲到vector的迭代器方面会详细介绍;
了解了上面的vector的定义之后,大家对于vector的插入和其所介绍的动态增长还有访问元素的高效,在末尾添加和删除元素相对高效。还没有体会到,接下来大家可以体会一下。
vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.pop_back();
for (auto e : v)
{
cout << e << " ";//auto关键字的使用 范围for
//底层还是由迭代器实现
}
注意:这里的insert和earse 并非简单的给一个数组下标的位置,或者要删除的元素的值,这里是需要通过迭代器来进行相关的操作的,具体的需要了解了迭代器的相关知识才可以,这里做一个简单的示例:
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int> ::iterator ite = v.begin()+1;
//也就是删除第二个位置的数据
v.erase(ite);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin(),5);
//相当于头插一个数据
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin() + 2, 8);
//在第指定一个位置插入一个数据
for (auto e : v)
{
cout << e << " ";
}
但是在一些情况下会出现迭代器失效的情况,会在以后的博客中详细介绍。
iterator的使用 | 接口说明 |
---|---|
begin() | 获取第一个数据位置的 iterator |
end() | 获取最后一个数据的下一个位置的iterator |
rbegin | 取最后一个数据的reverse_iterator |
rend() | 获取第一个数据的前一个位置的reverse_iterator |
cbegin() | 获取 第一个数据位置的const_iterator |
cend() | 获取最后一个数据的下一个位置的const_iterator |
我们知道 除了使用下标来访问vector 对象的元素外,标准库还提供了另一种访问元素的方法,使用迭代器(iterator) ,迭代器是一种检查容器内元素并遍历元素的数据类型。
标准库为每一种标准容器(包括 vector)定义了一种迭代器类型。迭代器 类型提供了比下标操作更通用化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现 代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下 标操作的 vector 类型也是这样
关于迭代器类型 ,迭代器失效等会单独整理一篇博客,这里我们主要知道如何利用 vector 的begin end rbegin rend cbegin cend进行vector 的遍历以及一些相关的操作。
每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。
这是 begin 的返回值 我们可以看到 它返回的是
序列容器开头的迭代器。
如果vector对象是Const限定的,则该函数将返回const迭代器。否则,它将返回迭代器.
成员类型迭代器和const迭代器是随机存取迭代器类型(分别指向元素和Const元素)。
而 end 是不是就返回容器 vector 的末尾的迭代器呢呢?
事实上end 返回的是 最后一个元素的下一个位置的迭代器。
那么具体如何进行容器元素的访问和操作呢?
vector<int>::iterator ite;//迭代器的定义 包括迭代器的类型 定义的变量
ite = v.begin();
while (ite != v.end())//不等于就保持其不会越界
{
cout << *ite << " ";//解引用来访问该位置的值
++ite;
}
rbegin 和 rend 返回的迭代器恰恰相反
相当于反向的 开头和末尾 只不过 rbegin 返回 的是容器的末尾的迭代器 而 rend 返回的是容器的开头的前一个位置的迭代器,而反向迭代器 在标准库中也有专门的定义
void Test9()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::reverse_iterator rite;//反向迭代器的定义 包括迭代器的类型 定义的变量
rite = v.rbegin();
while (rite != v.rend())//不等于就保持其不会越界
{
cout << *rite << " ";//解引用来访问该位置的值
++rite;
}
}
既然有了正向迭代器也有了反向迭代器 那么这个 const 迭代器是用来干什么的呢?
其实 使用迭代器不仅可以对范围内的数据进行访问也可以进行修改,而const迭代器的出现就是为了不让其对数据进行修改只进行访问,可以通过代码进行比较;
vector<int>::iterator ite = v.begin();
while (ite != v.end())
{
*ite += 1;
cout << *ite << " ";
++ite;
}
cout << endl;
vector<int>::const_iterator cite = v.cbegin();
while (cite != v.cend())
{
//*cite += 1;//报错表示左值不可修改
cout << *cite << " ";
++cite;
}
这就是使用迭代器进行访问查询 ,还有我们之前使用的下标访问,对下标进行遍历访问,
或者使用 auto 关键字 进行范围 for遍历;
说到 auto 关键字 它还可以推断出迭代器也可以进行遍历 比如
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
auto ite = v.begin();//自动识别其类型
while (ite != v.end())
{
cout << *ite << " ";
++ite;
}
容量空间 | 接口说明 |
---|---|
size() | 获取数据个数 |
capacity() | 获取容量大小 |
empty() | 判断是否为空 |
void resize (size_type n, value_type val = value_type()); | 改变vector的size |
void reserve (size_type n); | 改变vector放入capacity |
注意:
size();
返回一个vector 对象的大小 ,但是注意在使用下标访问的时候注意数组的下标是从 0 开始的 所以在使用下标遍历的时候需要size()-1;
capacity();
因为vector是动态增长的,因此其容量不够时就会增容,在vs里一般是 1.5倍的增长。
和string 一样 ,为了提高效率一般可以提前设置其容量减少开空间时花费的代价,提高效率。