1、概述
今天开始整理STL使用最广的容器了。同时这一部分也是我看了两遍,还有很多没有看懂的地方。最近准备硬着头皮,把这个山头给拿下,争取在第三遍能看懂吧。
容器,置物之所也。STL容器实际上就是把一些常用的数据结构给实现出来,方便大家使用。而常用的数据结构不外乎array、list、tree、stack、queue、hash table、set、map…等等。
根据 “数据在容器中的排列” 特性,这些数据结构可分为序列式和关联式两种。今天就整理部分序列式容器的内容。
2、序列式容器
所谓序列式容器,其中的元素都可序,但未必有序。C++语言本身提供了一个序列式容器array,STL另外提供了vector,list、deque、stack、priority-queue等等。今天就先整理vector。
2.1、vector实现
vector的数据安排以及操作方式,与array非常相似。两者唯一差别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变,需要更换更大的空间,需要客户端自己操作。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。
vector的大致结构如下:
template<class T, class Alloc = alloc> //使用默认的空间分配器
class vector
{
public:
//类型信息,我们之前整理的 trains 萃取技术,用于模板元编程
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage; //表示目前可用空间的尾
public:
iterator begin(); //返回开始迭代器
iterator end(); //返回结尾迭代器
size_type size(); //返回元素个数
size_type catacity(); //返回容器容量大小
bool empty(); //是否未空(尚未压入数据)
reference operator[](size_type n); //重载[] 运算符,支持下标
reference front(); //第一个元素
reference back(); //最后一个元素
void push_back(const T& x); //将元素插入至最尾端
void pop_back() //将最尾端元素取出
iterator erase(iterator position); //清除某个位置上的元素
void resize(size_type new_size, const T& x); //重新分配空间,以T初始化
void resize(size_type new_size); //重新分配空间
void clear(); //清空整个容器
......
};
vector维护的是一个连续线性空间,所以不论其元素型别为何,普通指针都可以作为 vector 的迭代器而满足所有必要条件,因为vector 迭代器所需的操作行为,如operator*,operator->,operator++,operator–,普通指针天生就具备。
vector支持随机存取,所以vector迭代器的类型为 random access iterator类型。
2.2、vector扩容
vector使用的线性连续空间,它的两个迭代器start 和 finish 分别指向配置得来的连续空间中目前已被使用的范围,而 end_of_storage 指向整块连续空间的尾端。
为了降低空间配置时的时间成本,vector实际配置的大小比客户端需求的更大一些,所以有了容量(capacity)的概念,如果满载,在压入新数据就扩容两倍。
需要注意的是,所谓动态增加大小,并不是在原有空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。
2.3、vector元素操作
其实这个没啥可整理的,大家看看也就明白了。
pop_back 将尾端元素拿掉,并调用对象析构函数
erase 清除元素,并把后面的元素前移
clear 清除所有元素
insert 插入元素,若是中间,则原有元素后移,可能引起扩容
push_back 最后面压入元素,可能引起扩容
2.4、总结
虽然说vector是可扩容的,但其实我们知道这是一个假象,如果扩容会执行下面三个步骤:
1、重新申请一块较大内存
2、将源数据复制过去
3、释放原空间
如果不是vector每次配置新空间时都会多分配一些内存,其“成长”的假象所带来的代价是相当高昂的。
感谢大家,我是假装很努力的YoungYangD(小羊)。
参考资料:
《STL源码剖析》