C++ STL 之 vector

"Standard Template Library"

Posted by leiyiming on June 20, 2016

STL库中常用的容器类 vector 详解,包括基础用法以及内存空间的操作。

vector 概述

vector 的数据安排以及操作方式与数组类似,唯一的区别在与数组是静态空间,一旦声明之后就无法自动改变空间大小,需要手动扩充空间大小。而 vector 是动态空间,随着元素的加入,其内部机制会自行扩充空间以容纳新元素。

vector 基本操作

vector 迭代器

其迭代器为普通指针。

template <class T, class Alloc = alloc>
class vector{
...
protected:
  iterator start;             //表示目前使用空间的头
  iterator finish;            //表示目前使用空间的尾(最后一个元素的下一个地址)
  iterator end_of_storage;    //表示目前可用空间的尾
...
}

其中,end_of_storagevector 的可用空间尾部,而不一定指向最后一个元素的下一个位置。详解见后续内存操作中。

vector 方法

iterator begin();        //返回使用空间头的迭代器
iterator end();          //返回使用空间尾的迭代器
size_type size();        //返回使用空间大小
size_type capacity();    //返回可用空间(已申请)大小
bool empty();            //返回是否为空
reference front();       //返回头元素的引用
reference back();        //返回尾元素的引用

void push_back(const T& x);  
                        //将元素压入末尾
                        
void pop_back();         //将尾端向前移动一个单位,放弃最后一个元素

iterator insert( iterator loc, const TYPE &val );
                         //在指定位置前插入元素val

void insert( iterator loc, size_type num, const TYPE &val );
                         //在指定位置前插入num个元素val

void insert( iterator loc, input_iterator start, input_iterator end );
                         //在指定位置前插入[start,end]区间所有元素

iterator erase(iterator pos);
                         //清除pos上的元素,返回原vector中pos指向的下一个元素的迭代器

void resize(size_type new_size);
                         //重新分配大小

void clear();            //清空所有元素

vector 的内存管理

vector 的使用空间大小与占用空间大小不一样,当往其尾部添加数据时,如果空间不足,它并不只会申请要添加的数据占用的空间大小,而是申请已有空间大小的二倍。所以占用空间总是大于等于已使用空间大小的。

vector 内存申请规则

利用一段代码来解释其内存空间申请规则,为了方便观察,我将运行结果写在了每行代码之后,下同:

int main()
{
	int i;
	vector<int> tv(2,9);
	cout<<"size= "<<tv.size()<<endl;                           //size =  2
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 2

	tv.push_back(1);
	cout<<"size= "<<tv.size()<<endl;                           //size =  3
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 4

	tv.push_back(2);
	cout<<"size= "<<tv.size()<<endl;                           //size =  4
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 4

	tv.push_back(3);
	cout<<"size= "<<tv.size()<<endl;                           //size =  5
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 8

	tv.push_back(4);
	cout<<"size= "<<tv.size()<<endl;                           //size =  6
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 8

	return 0;
}

可以很直观地得到以下结果: 当其初始化时,容量等于大小。当加入新元素之后的大小小于容量时,无需改变空间大小。当加入新元素之后的大小大于容量时,容量会扩大到原容量的两倍。 这样做避免了频繁地申请内存,移动数据,释放原内存。值得注意的是: 当 vector 的容量发生变化时,并不是在原有的空间接上新的空间,而是申请另外一块两倍大小的空间,所以指向原 vector 的迭代器会全部失效。

vector 内存空间的释放

继续来看一段代码:

int main()
{
	vector<int> tv(4,9);
	cout<<"size= "<<tv.size()<<endl;                           //size =  4
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 4


	tv.erase(tv.begin());
	cout<<"size= "<<tv.size()<<endl;                           //size =  3
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 4

	tv.clear();
	cout<<"size= "<<tv.size()<<endl;                           //size =  0
	cout<<"capacity= "<<tv.capacity()<<endl;                   //capacity = 4
	return 0;
}

可以从运行结果发现: 不论是 erase ,还是 clear 方法,都只能删除元素,无法删除占有的内存。 这样的设计是为了避免频繁的申请空间,所以当反复使用一个 vector 时,其优点就显现出来了。那如果要手动释放 vector 申请的内存空间怎么办呢?

答案是使用 swap 方法与空 vector 交换内存空间。


  vector<int>().swap(tv);

当使用 vector 的构造函数并且旧容器当参数时,生成的新容器只会申请旧容器所有元素大小的空间,并不会申请和旧容器占有空间一样大的空间,也就是没有申请旧容器没有使用的空间。然后使用 swap 方法,交换新旧空间,这样就可以达到释放未使用空间的目的。

所以,和空 vector 交换空间就可以达到释放旧容器的所有空间的目的。


后记

这篇博文记录了STL库的最常用的容器 vector 的用法和内存释放方法。