文章目录
- 1.vector源码分析
- 2.vector动态数组内部如何实现连续空间
- 3.内存分配子Allocator
1.vector源码分析
- capacity表示能够容纳的元素的个数,capacity()>=size(),size()表示当前所容纳的元素的个数
目的是:缓存一部分空间,下一次插入的时候,不需要分配内存,提供插入速度。 - eg:P73\01.cpp
#include <vector>
#include <iostrem>
using namespace std;
int main(void)
{
//vector<int> 是一个模板类,定义一个对象v会引发模板类的构造函数的调用
vector<int> v;//在这里打个断点,跟踪
return 0;
}
vector模板类应该传递2个参数,_Ax是有默认参数的,下面的代码是vector模板的实现
F11进去,这才是vector类模板的真正说明
template<class _Ty,
class _Ax = allocator<_Ty>>
class vector;
allocator本身是一个类模板,allocator<_Ty>是类模板的实例化,模板类_Ax
allocator就是一个内存分配子,stl会提供一个默认的内存分配子allocator<_Ty>,也可以实现为自己的内存方式(比如来自共享内存),
内存从哪里来,stl不关心
- 接下来,构造vector,会调用基类的构造函数_MyBase()
- 基类的类型使用typedef
- 继续go进去,传递一个参数进来,传参之前先调用_Alloc()构造函数初始化给_Al,这里会调用_All的拷贝构造函数
- _Alloc()构造函数,这里继续F11
- _Al调用拷贝构造函数,这里继续F11
- 要构造_Vector_val类模板,首先要构造基类_CONTAINER_BASE_AUX_ALLOC,然后再构造_Vector_val类模板对象成员,继续F11
- 首先要构造基类_CONTAINER_BASE_AUX_ALLOC<_Alloc>的解释
该基类又继承_Container_base基类,所以还要调用其基类的构造函数,继续go - 又是typedef,实际上_Container_base类就是_Container_base_secure,继续F11
- 调用_Container_base_secure构造函数来构造基类对象,将0初始化给_myfirstiter指针
- _Container_base_secure类的成员如下,但是是一个指针,就不会调用Iterator_base的构造函数
- 此外由于_Container_base基类(也是_contoiner_base_aux_alloc_empty,用了typedef)没有数据成员,就直接构造函数体,_Alloc是类型参数
- 接下来构造_Alval(_Al)对象成员,将内存分配子_Al对象初始化给_Alval(调用拷贝构造函数),但是担心_Alval和_Al类型不一样,所以使用了成员模板(在_Al中定义,在_Alval中使用),eg:_Ty传递是int类型,_Alloc传递是allocator<char>类型
_Ty是int类型
_Al的类型是allocator内存分配子模板类,即_Alloc;
typename表示_Alloc是一个模板,template rebind<_Ty>::other是一个类型,是_Alloc中的一个成员模板,将typename _Alloc template rebind<_Ty>::other看成是一个类型,将整个类型重新定义成_Alty - 光标放到红框处的_Alloc,继续go下去
- 再继续go
- 对象成员构造完毕
- 构造vector,基类的构造函数_MyBase()构造完毕,接下来会构造vector的数据成员,而vector内部只有3个指针,是不需要调用对象成员的构造函数(构造一个类,首先构造基类,再构造对象成员,最后是调用类构造函数本身)
- 构造vector,基类的构造函数_MyBase()构造完毕,构造vector的数据成员完毕,接着构造函数体Buy(0)
Buy(0)的意思是分配多少个空间 - vector内部的空间是连续的,但是又是可以动态增长的,这实际上是一个矛盾,解释如下:
内部能够容纳的元素个数:capacity()
vector内部所存放的元素个数:size()
上述2个值不一定总是相等的,导致vector内部多了3个指针
begin(),是闭区间,对应_Myfirst
end(),是开区间,对应_Mylast
只包含左边,不包含右边[ , )
_Myend是整个容量的最后一个位置的下一个位置 - 到目前为止初始化完毕
- eg:P73\02.cpp
仅仅跟踪push_back()的情况,而没有跟踪capacity()的情况 - 当前的size()等于0,capacity()也等于0,push_back表示从尾部插入。
- 在end()处,继续F11,跟踪下去
end()其实是依据_Mylast构造一个迭代器,也是一个模板类 - 调用模板类的构造函数
- 在insert(XXX)处F11,_Where表示当前插入的位置,返回的还是插入点的位置
_Insert_n往当前的插入点插入一个元素_Val - _Insert_n(XXX)中继续F11,当前的容量capacity()等于0,_Count表示插入的元素个数,为1;
max_size()表示能够容纳元素的最大个数 - 调整过后,_Capacity=1,也就是要分配一个空间(也就是说第一次要分配一个空间)
- max_size()中F11
- 当前传递_Ty的大小是4个字节(int类型),-1是32bit整数的最大值,二进制表示就是32个1,转成成十进制就是40几个亿,所以_Count
最多容纳10多亿个数据 - F10,直到返回
- eg:P73\02.cpp
#include <vector>
#include <iostrem>
using namespace std;
int main(void)
{
//vector<int> 是一个模板类,定义一个对象v会引发模板类的构造函数的调用
vector<int> v;
v.push_back(1);//在这打个断点
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
v.push_back(1);
cout<<v.capacity()<<endl;
return 0;
}
- 测试
- 大致是:增长的空间的算法是:增长为原来的一半;
第一次插入是1,0+0/2=0,0<0+1,所以当前容量为1;
第二次插入:由于是增长为原来的一半,1/2=0,0+1=1,所以只是增加1个空间,1<1+1,所以第二次插入时,当前的容量是1+1=2;
第三次插入,由于是增长为原来的一半,2/2=1,2+1=3,所以当前的容量是3;
以此类推
注意,当前元素个数是5,但是容量是6的情况,说明它缓存了一个空间,缓存空间的目的是:下一次插入的时候,不需要分配内存,是为了减少频繁分配内存。到时候容器中元素个数越多,缓存的空间也会越多,因为是缓存一半的空间。 - 总结:
capacity():内部能够容纳的元素个数
size():vector内部所存放的元素个数
通常capacity() >= size(),原因是:向量缓存了一部分的内存空间,用来容纳更多的元素,这样,下一次插入新元素的时候,就不必重新分配内存,提供了插入速度;
容量按照50%来扩充;
_Myfirst对应第一个元素(对应begin()),_Mylast对应最后一个元素的下一个位置(对应end()),_Myend对应最后一个空间的下一个位置 - 插入7个元素的空间分布如上:
插入第二个元素,空间扩充为原来的一半,1+1/2=1,因为不足以容纳2个元素,所以需要分配2个空间出来;
插入第6个元素的时候,缓冲了1个空间,下次插入的时候,就没必要分配空间了
2.vector动态数组内部如何实现连续空间
- 向量尾部插入法:
假设vector向量中有6个元素,其内存分布如下所示,要插入第7个元素,需要分配9个空间(此时这9个空间就是连续的),然后插入第7个元素,然后再把1-6给搬移下来 - 向量中间插入法
比如在4的位置插入7,同样会先分配9个空间,然后先插入7 - 然后将1-3搬下来,将4-6搬到7后面
3.内存分配子Allocator
略