前言
vector
容器应该算是最常用的容器之一了,它是序列式容器的一种,即元素可被排序。vector
被称为动态数组,可以随着元素的加入,内部的大小自行扩充。可能你对vector
的机制有所耳闻,比如每次扩充时,扩充当前容量的2倍等。接下来,我们就进入到它的源码来看看vector
是如何实现的。我们将它分为四部分进行分析,vector
的迭代器,vector
的内存分配及构造,vector
的内存控制以及一些常用操作
vector
的迭代器
首先vector
的迭代器是原生指针,可以根据traits
技法来获取到它的相应型别。当迭代器是原生指针时,我们无法利用模板的参数推导机制获取到它的相应型别,比如value_type
,而STL要求每一个迭代器都应该提供这5个内嵌相应型别,所以利用了模板的偏特化,针对原生指针。这点我们在讲解traits
这一小节中已经分析过了。
vector
里面内置了3个迭代器,分别是start
、finish
、end_of_storage
。这三者的作用分别是:
start
:指向vector使用的空间的头部finish
:指向vector使用的空间的尾部end_of_storage
:代表vector可用空间的尾部
vector
的空间分配及元素操作等,跟这三个迭代器的关系很密切。关于vector
迭代器的定义,我们直接看源码。
template <class T, class Alloc = alloc>
class vector {
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
//定义迭代器,就是一个普通的指针
//而random_access_iterator_tag可以提供的操作和普通操作一样
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
......
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
iterator start;
iterator finish;
iterator end_of_storage;
......
public:
iterator begin() { return start; }
const_iterator begin() const { return start; }
iterator end() { return finish; }
const_iterator end() const { return finish; }
......
在这段代码中,除了看到迭代器的声明之外,还看到了关于vector
自己的空间配置器,指定value_type
为申请单位。
最后还看到了我们再熟悉不过的begin()
、end()
这两个返回指向容器首、尾的迭代器的函数。在实现中,它们只是简单的返回了vector
内部的迭代器。
vector
内存分配及构造
这部分无疑是vector
最核心的部分。要想了解vector
内部是如何控制它的空间的,我们先从它的众多构造函数下手。
vector
的构造函数
vector
有以下几种构造函数:
vector() : start(0), finish(0), end_of_storage(0) {}
vector(size_type n, const T& value) { fill_initialize(n, value); }
vector(int n, const T& value) { fill_initialize(n, value); }
vector(long n, const T& value) { fill_initialize(n, value); }
explicit vector(size_type n) { fill_initialize(n, T()); }
vector(const vector<T, Alloc>& x) {
start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());
finish = start + (x.end() - x.begin());
end_of_storage = finish;
}
#ifdef __STL_MEMBER_TEMPLATES
template <class InputIterator>
vector(InputIterator first, InputIterator last) :
start(0), finish(0), end_of_storage(0)
{
range_initialize(first, last, iterator_category(first));
}
#else /* __STL_MEMBER_TEMPLATES */
vector(const_iterator first, const_iterator last) {
size_type n = 0;
distance(first, last, n);
start = allocate_and_copy(n, first, last);
finish = start + n;
end_of_storage = finish;
}
接下来我们依次来分析它们的源码。
vector() : start(0), finish(0), end_of_storage(0)
第一种构造函数即什么参数都不传,这个时候vector
的容量为0。使用vector<string> t
时调用。
vector(int n, const T& value)
这种构造函数与vector(int n, const T& value)
、
vector(long n, const T& value)
类似,只是因为n的类型不同重载了而已,但是它们内部调用的都是fill_initialize(n, value)
。当我们平时使用vector<string> v(5, "123")
这样的操作时就会调用该类构造函数。
explicit vector(size_type n)
这类构造函数只分配n个T的数量的空间,并且调用T的默认构造函数进行初始化。至于explicit
关键字,它是用来防止当只有一个传入参数时发生隐式转换的。比如这样的操作vector<int> v = 'a'
,如果没有加explicit
关键字,居然会调用成功,所以explicit
的重要不言而喻。
vector(const vector
/* 调用了allocate_and_copy函数分配内存及复制数据
* 并且维护这三个迭代器
*/
start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());
finish = start + (x.end() - x.begin());
end_of_storage = finish;
vector(InputIterator first, InputIterator last)
这种构造函数是根据迭代器first
和last
进行范围构造的。SGISTL中提供了两个版本,根据__STL_MEMBER_TEMPLATES
是否被宏定义了进行条件编译。
内存分配及初始化
以上便是vector
的构造函数,里面调用了fill_initialize
以及allocate_and_copy
还有range_initialize
这几个函数,我们来看看它们到底干了什么。
fill_initialize
/* 调用了allocate_and_fill函数来分配及初始化空间
* 维护了vector的三个迭代器
*/
void fill_initialize(size_type n, const T& value) {
start = allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
allocate_and_fill
iterator allocate_and_fill(size_type n, const T& x) {
//使用vector专属的空间配置器分配内存
iterator result = data_allocator::allocate(n);
/* __STL_TRY...__STL_UNWIND类似异常处理的try...catch语句块
* 这段代码的大意就是,初始化allocate分配的未初始化空间
* 如果失败了,则将分配的内存回收,防止内存泄露
*/
__STL_TRY {
uninitialized_fill_n(result, n, x);
return result;
}
__STL_UNWIND(data_allocator::deallocate(result, n));
}
allocate_and_copy
/* 与allocate_and_fill十分相似
* 唯一的不同的就是uninitialized_xxxx的不同调用
*/
template <class ForwardIterator>
iterator allocate_and_copy(size_type n,
ForwardIterator first, ForwardIterator last) {
iterator result = data_allocator::allocate(n);
__STL_TRY {
uninitialized_copy(first, last, result);
return result;
}
__STL_UNWIND(data_allocator::deallocate(result, n));
}
range_initialize
该函数根据迭代器的类型不同,重载了不同的版本。代码如下:
//针对InputIterator类型的迭代器
template <class InputIterator>
void range_initialize(InputIterator first, InputIterator last,
input_iterator_tag) {
for ( ; first != last; ++first)
push_back(*first);
}
// This function is only called by the constructor. We have to worry
// about resource leaks, but not about maintaining invariants.
//不是很理解为什么这里需要ForwardIterator类型的
//仔细检查了一下该函数内部调用的函数
//InputIterator类型的应该也可以满足
//不是很明白,望知道的告知,谢谢!
template <class ForwardIterator>
void range_initialize(ForwardIterator first, ForwardIterator last,
forward_iterator_tag) {
size_type n = 0;
distance(first, last, n);
start = allocate_and_copy(n, first, last);
finish = start + n;
end_of_storage = finish;
}
vector
内存的控制
前面我们看到了vector
的各种构造函数,以及如何分配内存及初始化的。
接下来,我们来分析vector
的内存控制,比如当最初申请的空间满了,是如何扩充的,以及清空vector
等操作。
它们主要跟push_back
、clear
等操作有关。
我们先来看看一些查看当前容量的函数。
size_type size() const { return size_type(end() - begin()); }
size_type max_size() const { return size_type(-1) / sizeof(T); }
size_type capacity() const {return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
max_size
代表当前最多能够存储的T类型的个数。
capacity
代表vector总空间的大小,目前正在使用的+预留的。
它是end_of_storage
与start
迭代器的距离。在初始时,end_of_storage = finish
,随着push_back
的调用,capacity
的大小会发生改变。
这里写了一段小代码,可以帮助我们理解capacity
和size
的区别,以及clear
函数的作用。
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> a(5);
cout << "capacity: " << a.capacity() << endl;
cout << "size: " << a.size() << endl;
a.push_back(1);
cout << "capacity: " << a.capacity() << endl;
cout << "size: " << a.size() << endl;
a.clear();
cout << "capacity: " << a.capacity() << endl;
cout << "size: " << a.size() << endl;
vector<int>().swap(a);
//{
// vector<int>temp;
// tepm.swap(a);
//}
cout << "capacity: " << a.capacity() << endl;
cout << "size: " << a.size() << endl;
return 0;
}
运行结果:
capacity: 5
size: 5
capacity: 10
size: 6
capacity: 10
size: 0
capacity: 0
size: 0
可以看到当初始状态时,capacity
和size
返回的大小相等,对应到内部就是end_of_storage
和finish
迭代器指向的位置相同。
当添加元素之后,capacity
的容量变成了原来的capacity
的2倍。
当我们使用clear
,只是调用了vector存储的元素的析构函数,但是vector本身的容量是没有释放的。
如果想释放内存,使用swap
函数即可,原理是将原本的vector的空间换一个临时vector变量,这样临时变量离开作用域时,就会自动调用析构函数释放内存。
在下一节中,我们将正式开始分析push_back
,通过它来了解上面的例子中,vector
内部执行的操作。
小结
本小节中我们主要了解了vector
的迭代器,以及它的空间配置器、构造函数,还有部分关于内存控制方面的,在下一小节,我们将主要针对push_back
等函数的操作,将vector
关于它内部空间的控制一探究竟,然后再分析部分的操作函数。