【STL深入理解】vector
这篇文章不打算讲述vector的基本用法,而是总结一下近期我大量阅读C++经典书籍时遇到的一些关于vector的容易忽略的知识点,特意将它们记录下来,以便以后查阅。
1.v[0]和v.at(0)的区别
void f(vector<int>& v)
{
    v[0];  //A
    v.at(0);  //B
}
观察该函数,我们使用A和B的形式来访问v的元素,他们有什么区别?他们唯一区别就是如果v空则B会抛出std::out_of_range的异常,至于A行为标准未加任何说明。所以B方式可以防止越界操作,但是B方式较A方式效率要低(因为加入了越界检查)。
2.resize()和reserve()是不同的操作
int main()
{
    vector<int> v;
    v.reserve(2);
    cout << "capacity: " << v.capacity() << endl;
    assert(v.capacity() == 2);
    v[0] = 1;  //A
    v[1] = 2;  //B
    return 0;
}
上面的代码是有问题的,我在VS2015下进行编译运行时弹出“vector越界访问”的错误。那该段程序错在哪里?
- 首先这里的断言可能会失败。因为reserve操作将保证vector容量>=2。而且这里的断言也是多余的。
- 然后A和B的复制是有问题的,因为该程序忽视了resize/size和reserve/capacity的区别。size()告诉你容器中实际有多少个元素,resize()则会在容器末尾添加或者删除元素,使得容器达到指定大小;capacity()告诉你最少添加多少个元素才会导致容器的重新分配内存,而reserve()在必要时候总是容器内部缓冲区扩至一个更大的内容,reserve()并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。
所以上面的代码修改可以这么做:
int main()
{
    vector<int> v;
    v.resize(2);
    v[0] = 1;
    v[1] = 2;
    return 0;
}
或者使用pushback()来添加元素。
3.vector增长的方式
上面第二点说了vector中size()和capacity()是不同的意思,造成这一现象的原因就是vector的的增长方式有些不同。用一句话来总结vector的的增长方式就是“重新配置,移动数据,释放原空间”。
标准库实现者为了尽量减少容器空间重新分配次数,他们采取这样一种策略:当不得不获取新的内存空间时,vector通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可以用来保存更多的元素。这样,就不需要每次添加新元素都重新分配容器空间了。上面说到“容器预留这些空间作为备用”,这里的预留空间时多少呢?根据《STL源码剖析》的说法,当增加新元素时,如果超过当时的容量,则容量会扩充至原理容量的2倍;如果两倍仍不足,就扩张到足够大的容量。
vector中有三个重要迭代器:

以下图像形象表示出vector的增长方式以及size与capacity的区别。

为了验证vector内存容量的增长策略,我特意做了以下实验。
以下代码在centos 7下编译运行,其显示结果与《STL源码剖析》说法一致。
#include <iostream>
#include <vector>
using namespace std;
//在g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11)下编译运行
int main()
{
    vector<int> v(2,9);  //9 9
    cout << "size=" << v.size() << endl;  //size=2
    cout << "capacity=" << v.capacity() << endl; //capacity=2
    v.push_back(1);  //9 9 1
    cout << "size=" << v.size() << endl;  //size=3
    cout << "capacity=" << v.capacity() << endl; //capacity=4
    v.push_back(2); //9 9 1 2
    cout << "size=" << v.size() << endl;  //size=4
    cout << "capacity=" << v.capacity() << endl; //capacity=4
    v.push_back(3);  //9 9 1 2 3
    cout << "size=" << v.size() << endl;  //size=5
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    v.push_back(4);  //9 9 1 2 3 4
    cout << "size=" << v.size() << endl;  //size=6
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    v.push_back(5);  // 9 9 1 2 3 4 5
    cout << "size=" << v.size() << endl;  //size=7
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    v.pop_back();
    v.pop_back();
    cout << "size=" << v.size() << endl;  //size=5
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    v.pop_back();
    v.pop_back();
    cout << "size=" << v.size() << endl;  //size=3
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    v.clear();
    cout << "size=" << v.size() << endl;  //size=0
    cout << "capacity=" << v.capacity() << endl; //capacity=8
    return 0;
}
但是在VS2015下编译运行,效果就不一样了。

查阅资料知道,这里容量增长方式是:每次扩容50%
依次看来,在VS2015下采用的标准库版本与Linux下的版本应该是不一样的。
4.迭代器失效情况总结
对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效。为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了10个元素,现在又要添加一个元素到容器中,但是内存中紧跟在这10个元素后面没有一个空闲空间,而vector的元素必须顺序存储一边索引访问,所以我们不能在内存中随便找个地方存储这个元素。于是vector必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间的元素被复制到新的存储空间里,接着插入新的元素,最后撤销旧的存储空间。这种情况发生,一定会导致vector容器的所有迭代器都失效。
先看一个错误的例子:
vector<mission>::iterator itr = vm.begin();
while (itr != vm.end())
{
    if ((*itr).getStartTime() <= nowTime)
    {
        vm.erase(itr);
    }
    itr++;
}
这段代码运行起来会crash,其原因当然是迭代器失效了,我们还用了它。
因为在erase操作后,原迭代器是相当于一个野指针的状态,对其++必定出错。
而erase的返回值就是指向被删除的元素的下一个元素的迭代器,我们没必要再次++了。
正确写法:
vector<mission>::iterator itr = vm.begin();
while (itr != vm.end())
{
    if ((*itr).getStartTime() <= nowTime)
    {
        itr = vm.erase(itr);
    }
    else
    {
        itr++;
    }
}
vector迭代器的几种失效的情况:
- 当插入一个元素后,插入位置之后的元素迭代器肯定失效。
- 当插入一个元素后,capacity返回值与没有插入元素之前相比有改变(即存储空间重新分配),则需要重新加载整个容器,此时指向容器的迭代器都会失效。
- 当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。
【STL深入理解】vector的更多相关文章
- C++ STL库之vector
		vector直译有"容器"之意,我们可以把它理解成是一个不限长度的数组. 我们可以通过代码进一步理解vector. 示例代码如下: #include <stdio.h> ... 
- 转:用STL中的vector动态开辟二维数组
		用STL中的vector动态开辟二维数组 源代码:#include <iostream>#include <vector>using namespace std;int mai ... 
- STL中的Vector相关用法
		STL中的Vector相关用法 标准库vector类型使用需要的头文件:#include <vector>. vector 是一个类模板,不是一种数据类型,vector<int> ... 
- (转)C++ STL中的vector的内存分配与释放
		C++ STL中的vector的内存分配与释放http://www.cnblogs.com/biyeymyhjob/archive/2012/09/12/2674004.html 1.vector的内 ... 
- C++STL中的vector的简单实用
		[原创] 使用C++STL中的vector, #include <stdio.h> #include<stdlib.h> #include<vector> usin ... 
- STL中的vector实现邻接表
		/* STL中的vector实现邻接表 2014-4-2 08:28:45 */ #include <iostream> #include <vector> #include ... 
- C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法
		每次忘记都去查,真难啊 /* C/C++解题常用STL大礼包 含vector,map,set,queue(含优先队列) ,stack的常用用法 */ /* vector常用用法 */ //头文件 #i ... 
- C++标准模板库(STL)之Vector
		在C中,有很多东西需要自己实现.C++提供了标准模板库(Standard Template Libray,STL),其中封装了很多容器,不需要费力去实现它们的细节而直接调用函数来实现功能. 具体容器链 ... 
- STL容器之vector
		[1]模板类vector 模板类vector可理解为广义数组.广义数组,即与类型无关的数组,具有与数组相同的所有操作. 那么,你或许要问:既然C++语言本身已提供了一个序列式容器array,为什么还要 ... 
随机推荐
- Java基础(十一)  Stream I/O and Files
			Java基础(十一) Stream I/O and Files 1. 流的概念 程序的主要任务是操纵数据.在Java中,把一组有序的数据序列称为流. 依据操作的方向,能够把流分为输入流和输出流两种.程 ... 
- 5个步骤,将 storyboard 从 iphone 版转变为 ipad 版
			1.将 iPhone 版的 Storyboard 复制为 iPad 的,比如 Main_iPad.storyboard 2.用文本编辑器(不要用 Xcode)打开 Main_iPad.storyboa ... 
- struts2 maven整合tiles3
			最新项目发现使用tiles能够很好的将多个页面组合起来,以下就是配置信息,使用tiles3 1.首先配置maven pom.xml加入例如以下: <dependency> <grou ... 
- JavaScript操作符(一元操作符)
			JavaScript操作符包括算术操作符.位操作符.关系操作符和相等操作符.只能操作一个值的操作符叫做一元操作符. 递增和递减操作符 递增和递减操作符有两个版本:前置型和后置型.前置型操作符位于要操作 ... 
- js 数组的常用方法归纳
			数组的常用方法归纳 slice(start,end) 传参:start代表从哪里开始截取,end代表截取结束的地方 var a = [1,2,3]a.slice(1);//[2,3] pop() 可以 ... 
- 【Hdu2089】不要62(数位DP)
			Description 题目大意:给定区间[n,m],求在n到m中没有"62"或"4"的数的个数. 如62315包含62,88914包含4,这两个数都是不合法的 ... 
- 来腾讯云开发者实验室 学习.NET
			腾讯云开发者实验室为开发者提供了一个零门槛的在线实验平台,开发者实验室提供的能力: 零门槛扫码即可免费领取实验机器,支持使用自有机器参与,实验完成后支持保留实验成果: 在线 WEB IDE 支持 sh ... 
- MySQL 如何存储长度较大的varchar与blob
			本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/96 最近,在工作中遇到了MySQL中如何存储长度较长的字段类型问 ... 
- Expression Blend4安装破解
			先在官网上下载Expression Blend4试用版 首先进入微软下载中心,http://www.microsoft.com/zh-cn/download/default.aspx: 搜索Expre ... 
- (一)DOM 常用操作 —— “查找”节点
			在 DOM 树中,如果想要操作一个节点,那么首先要"查找"到这个节点.查找节点的方法由 Document 接口定义,而该接口由 JavaScript 中的 document 对象实 ... 
