Vector模拟一个动态数组,其内存模型如下
Vector的能力
vector将元素复制到内部的dynamic array中,是一个有序群集;vector支持随机存取,vector迭代器是一个随机存取迭代器 ,所以对任何一个STL算法都可以。
大小(size)和容量(capacity)
为什么vector比容器的通用操作多了个capacity()?就是因为
vector优异性能就是配置比其容纳的元素所需更多的内存。其中capacity()返回vector实际能够容纳的元素数量,如果超越了这个数量,vector就有必要重新配置内部存储器
vector的容量很重要,主要有以下两点
1. 一旦内存重新分配,和vector元素相关的所有references,pointers,iterators都会失效
2. 内存重新配置很耗时间
当然,可以使用reserve()保存适当容量,避免重新分配,且reverse()操作不会改变容器的size和元素
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
int arr1[] = {1,2,3};
vector<int> v1(arr1,arr1+sizeof(arr1)/sizeof(arr1[0]));
vector<int>::const_iterator it = v1.begin();
cout << "size:" << v1.size() << endl; //3
cout << "capacity:" << v1.capacity() << endl; //3
cout << "begin iterator :" << *it << endl; //1
v1.reserve(80); //reserve后重新分配分配内存空间,所有references,pointers,iterators都会失效
cout << "size:" << v1.size() << endl; //3
cout << "capacity:" << v1.capacity() << endl;//80
cout << "begin iterator :" << *it; //一个杂乱的数字
return 0;
}
/*
如上注释,所有reserve一般在元素还未装入容器中时就调用了,一般也这样使用,在刚创建vector时还未对其容器中元素赋值时进行reverse
vector<int> v;
v.reserve(80);
...//赋值操作
*/
注意:vector不能使用reserve()来缩减容量,如果reserve()的参数比当前vector的容量还小,则不会产生任何作用。这里有个间接缩减vector容量的小窍门,如下代码
再看一个例子缩减容量的
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void shrinkCapacity(vector<T>& v)
{
vector<T> tmp(v);
v.swap(tmp);
}
int main(void)
{
int arr1[] = {1,2,3};
vector<int> v1;
v1.reserve(30);
v1.assign(arr1,arr1+sizeof(arr1)/sizeof(arr1[0]));
vector<int>::const_iterator it = v1.begin();
cout << "before shrinkCapacity :" << endl;
cout << "size:" << v1.size() << endl;//3
cout << "capacity:" << v1.capacity() << endl;//30
cout << "begin iterator :" << *it << endl;//1
cout << endl;
cout << "after shrinkCapacity :" << endl;
shrinkCapacity(v1);
cout << "capacity:" << v1.capacity() << endl;//3
cout << "begin iterator :" << *it << endl; //出现的值奇怪
//所以应该更新一下才行
it = v1.begin();
cout << "begin iterator :" << *it << endl;//1
return 0;
}
不过注意,swap()后原先所有的references,pointers,iterators都会失效。
甚至可以直接调用
vector<T>(v).swap(v); //其实就是创建一个临时变量与v交换
/*
vector<T>(v); 创建一个临时变量,并用v进初始化
*/
另外一个避免重新分配内存的是,初始化期间就想构造函数传递附加参数
vector<T> v(5); //创建一个有5个值得vector,但是会调用5次default构造函数
如果这样做仅是为了保留足够内存,建议还是使用reserve()。
事实上,为了防止内存破碎,在很多STL版本中,容量的增大幅度很大,即使你不调用reserve(),当你第一次安插元素时,也会一口气配置整块内存(如2K)。
既然vector的容量不会缩减,删除元素,其references,pointers,iterators也会继续有效,然而安插操作可能使其失效(因为安插可能导致vector重新分配空间)
Vector的操作函数
- 构造、拷贝和析构
- 非变动性操作
- 赋值
上图列出了“将新元素赋值给vector,将旧元素全部移除”的方法,所有赋值操作都可能会调用元素型别的default构造函数,copy构造函数 ,assignment操作符和或析构函数 - 元素存取
只有at会进行越界检查,其他函数不做检查 - 迭代器相关函数
- 安插和移除元素
需要注意的是:安插或移除元素都会使“作用点”之后的元素的references,pointers,iterators失效。安插操作若是引得vectro重新分配内存,那么该容器身上的所有references,pointers,iterators都会失效
vector没有提供删除与val相等的所有元素,因此需要借助算法。
栗子,删除与val相等的第一个元素
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(const vector<int>& v)
{
vector<int>::const_iterator it = v.begin();
for (; it!=v.end(); ++it)
cout << *it << " ";
cout << endl;
}
int main(void)
{
double val = 3;
int arr[] = {1,2,3,4,3,25,3};
vector<int> v(arr,arr+sizeof(arr)/sizeof(arr[0]));
print(v);
vector<int>::iterator it; //刚开始设置为const_iterator
it = find(v.begin(),v.end(),val);
if (it != v.end())
v.erase(it);
print(v);
return 0;
}
/*
犯了一个错误:见标识处,而erase()里的参数中的iterator不是const型的
*/
将vector当作一般Arrays使用
简单的说只要你需要动态数组,你就可以使用vector。
看一个例子
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
void print(const vector<char>& v)
{
for (int i=0; i<v.size(); ++i)
cout << v[i] << " ";
cout << endl;
}
int main(void)
{
vector<char> v;
v.resize(40);
strcpy(&v[0],"hello");
cout << &v[0] << endl;
//cout << v.begin() << endl; 错误,might work, but not portable
print(v);
return 0;
}
/*
如今vector就像一个连续数组一样,可以通过其首地址&v[0]来打印字符串的值。
*/
注意:千万不要把迭代器当作第一元素的地址来传递,vector迭代器是由实作版本定义的,也许不是个一般指针
还有如果要存字符串数组的话,建议用vector.
补充
swap还可以交换元素的值,不止是交换两个容器
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int main(void)
{
vector<int> v;
v.push_back(2);
v.push_back(3);
swap(v[0],v[1]);
cout << v[0];
cout << v[1];
}
总结
vector比容器通用的操作多出以下几个成员函数
capacity();
reserve();
assign(n,elem);
assign(beg,end);
push_back();
pop_back();
resize();
可以看出:
1. 多出的函数有不少是和容量有关的,所以说弄清楚vector的容量很重要
2. 另外的就是和元素插入删除有关的函数,主要是和vector特性有关,都只在尾部进行相应动作
3. 当然vector也可以在中间插入,但是成本大,不划算
4. vector当容量不足时,一般的实作版本会将容量扩充一倍
错误
由于时常回过头来看看,经常发现问题并记录下来。
- 关于reserve()操作,先看一个例子
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <iterator>
using namespace std;
int main(void)
{
vector<int> col;
col.reserve(10);
col[0] = 1;
col[1] = 2;
cout << col[0] << " " << " " << col[1] << endl;
copy(col.begin(),col.end(),ostream_iterator<int>(cout," "));
return 0;
}
/*
这代码在DevC++(gcc)和vs08下都能编译通过,
但是DevC++下能运行,结果:1 2(当然最后一句打印容器的值是没有值的)
在 vs08下运行会出错
*/
原因:reserve()操作不会改变容器的元素和容器的size(可通过size()操作返回),而operator[]操作不做边界检查,可能越界,因此编译不会出错,但是运行会出错。
总结:注意STL的大多数操作都不提供边界检查,因此需要特定注意,尤其在对迭代器进行操作时