四月 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++特性,那么针对容器的操作要么成功,要么不会产生影响。