c++标准库——vector容器

四月 8th, 2010 by ewangplay





1 vectors容器的能力



size和capacity的区别:vector容器除了提供通用的三个跟元素数目有关的函数size(), empty()和max_size(),另外还提供了一个独特的capacity()函数,那么这个capacity()和size()的区别是什么呢?


  • size(): 返回容器中实际的元素数目。
  • capacity(): 返回容器在重新扩充内存之前可能容纳的最多元素数目。
  • max_size(): 返回容器在当前系统框架下可能容纳的最多元素数目。

我们可以理解为vector容器会预留一段内存空间,这个内存空间可能会容纳10个元素,但目前只插入了5个元素,那么size()返回的就是5,而capacity()返回的就是10。当vector目前的内存空间不足以容纳新的元素时,就会自动扩充内存空间,而这个过程会产生两点影响:



  • 重新分配内存会使所有针对容器中元素的引用、指针和iterator失效。
  • 重新分配内存会浪费时间,降低性能。

为了不至于使扩充内存的现象频繁发生,我们可以根据需要提前为vecotor容器预留足够大的内存空间,可以使用reserve()函数来预留元素的内存空间:



std::vector<int> col1;    //create a empty vector
 
 
col1.reserve(80);           //reserve memory for 80 elements


另外也可以通过为vector的构造函数传递参数来预留内存空间:



std::vector<T> col1(10);    //create a vector and initialize it with 10 elements


                                        //calls 10 times the default constructor of type T
当然,使用这种方法的前提是容器元素类型必须提供缺省的构造函数。但是对于复杂的数据类型,这种初始化要占用一定的时间。如果你的目的仅仅是为了预留一定的元素空间,优先使用reserve()函数,它只是预留内存空间,并不进行元素的初始化。




通过下面简单的例子我们就可以看的更清楚:

#include <iostream>
 
  
#include <vector>
 
  
using namespace std;
 
  
int main()
 
  
{
 
  
    vector<int> col1;
 
  
    col1.reserve(10);
 
  
    col1.push_back(2);
 
  
    col1.push_back(5);
 
  
    cout << "col1 size:" << col1.size() << endl;
 
  
    cout << "col1 capacity:" << col1.capacity() <<endl;
 
  
    cout << endl;
 
  
 
  
    vector<int> col2(10);
 
  
    cout <<"col2 size:" << col2.size() << endl;
 
  
    cout << "col2 capacity:" <<col2.capacity() <<endl;
 
  
}


运行结果如下:


col1 size:2
 
 
col2 capacity:10
 
 

 
 
col2 size:10
 
 
col2 capacity:10



值得注意的是,vector容器可以扩充内存空间,却不可以缩小内存空间。给reserve()函数传递一个小于当前容器capacity值的参数是no-op(无效果操作)。正是因为vector容器不能缩小内存空间,所以当删除和修改元素时,可以保证被操作元素之前的针对该容器元素的所有引用、指针、iterator仍然有效;但是插入操作就不能保证这种有效性了。
虽然不能直接缩小vector容器的capacity,但通过辅助手段还是可以一定程度缩小这个capacity值。当使用swap()交换两个vector容器的内容时,其对应的capacity也相应交换。基于这个理论,我们可以设计下面的模板函数,用来在保留容器元素的情况下缩小容器的capacity值:




template <typename T>
 
 
void shrinkCapacity(std::vector<T>& v)
 
 
{   
 
 
    std::vector<T> tmp(v);    //copy elements into a new vector
 
 
    v.swap(tmp);                   //swap internal vector data
 
 
}



你也可以把上面函数浓缩成一个语句:




std::vector<T>(v).swap(v);    //shrink capacity of vector for type T






2 vector容器的操作



Element access
vector容器为元素的直接访问提供了四个操作符:


  • c.at(idx): 返回idx索引处的元素,进行索引值的有效性检查,如果超出了有效范围,抛出out_of_range异常。
  • c[idx]: 返回idx索引处的元素,不进行索引值的有效性检查。
  • c.front(): 返回第一个元素,不进行存在性检查。
  • c.back(): 返回最后一个元素,不进行存在性检查。

跟数组类似,vector容器的元素索引值也从0开始计数,所以最后一个元素的索引值是size()-1。对于nonconstant(非常量)vector容器,上面四个操作符返回对应元素的引用(reference),可以通过这些操作符直接修改元素的值。值得注意的是,只有at()操作符进行索引值有效性检查,如果无效抛出out_of_range异常,其他三个操作符都不进行有效性检查,如果idx无效或者容器为空,那么[], front(), back()的行为没有定义(不确定):



std::vector<int> col1;    //create a new empty vector
 
 
col1[5] = 3;                  //runtime error? undefined behavior
 
 
cout << col1.front();      //runtime error? undefined behavior
 
 
col1.at(5) = 3;               //throws out_of_range exception



所以,为了程序的健壮性,你必须自己进行有效性检查:




std::vector<int> col1; 
 
 
......
 
 
if (col1.size() > 5)
 
 
{
 
 
    col1[5] = 3;
 
 
}
 
 
if (!col1.empty()) 
 
 
{
 
 
    cout << col1.front();
 
 
}



3 vector容器作为普通数组
虽然c++标准中没有明确地指出vector容器的元素是否排列在连续的内存空间中,但事实上这一点是得到保证的。所以下面语句对于有效的索引值i结果是为true:



&v[i] == &v[0] + i



这个保证意味着我们可以把vector容器作为普通的动态数组来使用,比如:




std::vector<char> v;
 
 
v.resize(41);            //make room for 41 characters (include '\0')
 
 
strcpy(&v[0], "hello, world!");
 
 
printf("%s\n", &v[0]);



当然在使用之前,我们要使用resize()或者reserve()来保证动态数组有足够的内存空间来容纳复制的数据。而且,当作为动态字符数组来使用时,要特别注意字符串后面要以'\0'字符结束。




需要注意的是,不可以把第一个元素的Iterator作为第一个元素的地址来使用,因为iterator跟实现相关,它的含义可能跟普通指针大相径庭。


printf("%s\n", v.begin());    //error!
 
 
printf("%s\n", &v[0]);        //ok!



4 vector容器的异常处理



针对vector容器的异常处理,c++标准库保证下面事项:


  • 使用push_back()插入元素发生异常,保证对容器内容没有影响。
  • 使用insert()插入元素发生异常,如果插入元素的复制操作(拷贝构造函数和赋值操作)不会抛出异常,保证对容器内容没有影响。
  • pop_back()不会抛出异常。
  • erase()和clear()不会抛出异常。
  • swap()不会抛出异常。
  • 如果容器元素是简单数据类型,不使用复杂的c++特性,那么针对容器的操作要么成功,要么不会产生影响。