SGISTL源码探究-vector容器(上)
来源:互联网 发布:mac foxmail 邮件备份 编辑:程序博客网 时间:2024/05/21 05:18
前言
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_TEMPLATEStemplate <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: 5size: 5capacity: 10size: 6capacity: 10size: 0capacity: 0size: 0
可以看到当初始状态时,capacity
和size
返回的大小相等,对应到内部就是end_of_storage
和finish
迭代器指向的位置相同。
当添加元素之后,capacity
的容量变成了原来的capacity
的2倍。
当我们使用clear
,只是调用了vector存储的元素的析构函数,但是vector本身的容量是没有释放的。
如果想释放内存,使用swap
函数即可,原理是将原本的vector的空间换一个临时vector变量,这样临时变量离开作用域时,就会自动调用析构函数释放内存。
在下一节中,我们将正式开始分析push_back
,通过它来了解上面的例子中,vector
内部执行的操作。
小结
本小节中我们主要了解了vector
的迭代器,以及它的空间配置器、构造函数,还有部分关于内存控制方面的,在下一小节,我们将主要针对push_back
等函数的操作,将vector
关于它内部空间的控制一探究竟,然后再分析部分的操作函数。
- SGISTL源码探究-vector容器(上)
- SGISTL源码探究-vector容器(下)
- SGISTL源码探究-list容器(上)
- SGISTL源码探究-deque容器(上)
- SGISTL源码探究-list容器(下)
- SGISTL源码探究-deque容器(下)
- SGISTL源码探究-关联式容器:set
- SGISTL源码探究-关联式容器:map
- SGISTL源码探究-关联式容器:multiset
- SGISTL源码探究-关联式容器:multimap
- SGISTL源码探究-关联式容器:hash_set
- SGISTL源码探究-关联式容器:hash_map
- SGISTL源码探究-关联式容器:hash_multiset
- SGISTL源码探究-关联式容器:hash_multimap
- SGISTL源码探究-STL中的红黑树(上)
- SGISTL源码探究-STL中的hashtable(上)
- SGISTL源码探究-配接器
- SGISTL源码探究-内存池
- phpcms v9.6.0版本getshell测试
- 快捷键大全-私人收藏
- [leetcode]664. Strange Printer
- 常见排序算法(时,空)复杂度总结及稳定性分析
- CentOS7.3安装部署wordpress
- SGISTL源码探究-vector容器(上)
- 98. Validate Binary Search Tree
- [单调队列] POJ2823
- ICPC2017网络赛(乌鲁木齐)E: Half-consecutive Numbers (大数)
- vyos
- Java NIO 介绍
- NOIP2016组合数问题(洛谷2822)
- html2canvas如何截高清全图
- Android中AsyncTask面试相关知识