什么是vector
vector是可变大小的数组,支持快速随机访问,在尾部之外的位置插入或者删除元素可能很慢。
为什么会有vector?
vector的数据安排以及操作方式,与数组非常相似,两者的唯一差别在于空间运用的灵活性。数组是静态空间,一旦分配空间后就不能改变其大小,如果想要扩容或者是减容,操作比较繁琐。vector是动态空间,随着元素的加入,他的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的利用与使用的灵活性有很大的提高,我们再也不必害怕空间不足而一开始就分配一个大数组了。
vector的迭代器
vector维护一个线性空间,所以不论元素的型别如何,普通指针都可以作为vector的迭代器,因为迭代器的功能,如operaroe*, operator->, operator++, operator–, operator+, operator-, operator+=, operator-=,这些操作普通指针天生具备,vector支持随机存取,而普通指针正有着这样的能力。所以vector提供的是随机访问迭代器。
vector的数据结构
vector所采取的数据结构非常简单,线性连续空间,迭代器_Myfirst和_Mylast分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。
为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这便是容量的概念。换句话说,一个vector的容量永远大于或者等于其有效长度,一旦容量等于其有效长度,便是满载,下次再有新增元素,整个vector容器就得进行扩容。
所谓的动态增加大小,并不是原空间之后连续开辟空间(因为无法保证源空间之后尚有可分配内存),而是一块更大的内存空间,然后将原数据拷贝到新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新分配,指向原vector的所有迭代器都就失效了。
构造函数
函数名称 | 函数功能 |
---|---|
vector | 采用模板实现类实现,默认构造函数 |
vector(v.begin(), v.end()) | 将v[begin(), end())区间中的元素拷贝给本身 |
vector(n, elem) | 构造函数将n个elem拷贝给本身 |
vector(const vector &vec) | 拷贝构造函数 |
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
赋值操作
函数名称 | 函数功能 |
---|---|
assign(beg, end) | 将[beg, end)区间中的数据拷贝赋值给本身 |
assign(n, elem) | 将n个elem拷贝赋值给本身 |
vector& operator=(const vector &vec) | 重载等号操作符 |
swap(vec) | 将vec与本身的元素互换 |
// 巧用swap收缩空间
void test03()
{
vector<int> v;
for(int i = 0; i < 100000; i++)
{
v.push_back(i);
}
cout << "v的容量 " << v.capacity() << endl;
cout << "v的大小 " << v.size() << endl;
v.resize(3);
cout << "v的容量 " << v.capacity() << endl;
cout << "v的大小 " << v.size() << endl;
// 巧用swap
// 默认构造匿名对象,然后交换空间,匿名对象在执行完改行代码后就
// 被释放掉了,所以130000也被释放了
vector<int> (v).swap(v);
cout << "v的容量 " << v.capacity() << endl;
cout << "v的大小 " << v.size() << endl;
}
迭代器操作
函数名称 | 函数功能 |
---|---|
begin() | 获取第一个数据位置的迭代器 |
end() | 获取最后一个数据的下一个位置的迭代器 |
rbegin() | 获得最后一个数据位置的反向迭代器 |
rend() | 获得第一个位置的前一个位置的反向迭代器 |
cbegin() | 获得第一个数据位置的const迭代器 |
cend() | 获得最后一个数据的下一个位置的const迭代器 |
容量操作
函数名称 | 函数功能 |
---|---|
size() | 返回容器中元素的个数 |
empty() | 判断容器是否为空 |
resize(int num) | 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。 |
capacity() | 容器的容量 |
reserve(int len) | 容器预留len个元素长度,预留位置不初始化,元素不可访问 |
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。如果指定的参数小于当前的capacity,则不会产生任何效果。只有当参数大于capacity时才有意义。
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增加的。不要固化的认为,顺序表增容都是2倍。这里是存在问题的!,当我们使用insert方法完成数据插入的时候,如果插入数据量大于当前容器所有的数据总量的时候,那么增加的容量大小为待插入数据的总量。
resize在开辟空间的同时还会进行初始化,影响size
void test04()
{
vector<int> v;
//v.reserve(100000);
int *p = NULL;
int num = 0;
for(int i = 0; i < 100000; i++)
{
v.push_back(i);
if(p != &v[0])
{
p = &v[0];
cout << v.capacity() << endl;
num++;
}
}
cout << num << endl;
// 开辟100000数据用了多少次
}
数据存取操作
函数名称 | 函数功能 |
---|---|
at(int idx) | 返回索引idx所指的数据,如果idx越界,抛出out_of_range异常 |
operator[] | 返回索引idx所指的数据,越界时,运行直接报错 |
front() | 返回容器中第一个数据元素 |
back() | 返回容器中最后一个数据元素 |
void test08()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
for(auto x : v)
{
cout << x << " ";
}
cout << endl;
}
增删改查
函数名称 | 函数功能 |
---|---|
void push_back(const value_type &val) | 尾插 |
void pop_back() | 尾删(并不会改变容量) |
iterator insert(iterator position, const value_type &val) | 在position之前插入val |
iterator erase(iterator postion) | 删除position位置的数据,不改变底层容量 |
void clear() | 清空vector,但不改变底层容量 |
迭代器失效的情况
void test09()
{
int a[] = {1,2,3,4};
vector<int> v(a, a + sizeof(a) / sizeof(a[0]));
// 使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据,导致pos迭代器失效
v.erase(pos);
cout << *pos << endl;//此处会导致非法访问
// 在pos之前插入数据,可能会导致迭代器失效
// 因为insert可能会导致扩容
pos = find(v.begin(), v.end(), 3);
v.insert(pos, 30);
cout << *pos << endl;
}
void test10()
{
int a[] = {1,2,3,4};
vector<int> v(a, a + sizeof(a) / sizeof(a[0]));
// 实现删除v中所有偶数
// 如果是偶数,erase将会导致it失效
// 对于失效的it进行++,会导致程序崩溃
vector<int>::iterator it = v.begin();
# if 0
while(it != v.end())
{
if(*it % 2 == 0)
{
v.erase(it);
}
++it;
}
#endif
while(it != v.end())
{
if(*it % 2 == 0)
{
it = v.erase(it);
}
else
{
++it;
}
}
for(auto x : v)
{
cout << x << endl;
}
}
如果你在使用c++11的新特性的话,一定要加上-std=c++11选项