Array表示分配后不可更改的数组。
Vector表示分配后可以更改的数组,具体起什么名字以后再说吧。
vector采用了连续内存空间的布局,
start指向首元素所在的位置
finish指向最后一个有效元素所在的后一个位置,这样在新增元素的时候直接就可以放在finish所指的位置,然后再将finish自增即可。
end指向最后一个可用空间的后一个位置。遵循左闭右开。
template<typename _Tp, typename _Alloc>
class vector
{
private:
_Tp* _start;
_Tp* _finish;
_Tp* _end;
}
vector的底层通过malloc分配空间(或者也可能是operator new)。众所周知,malloc函数是不会调用类的构造函数的。如果我传递进去的模板参数是一个类类型,这样岂不是出大问题了。
其实不然,STL直接将空间分配与元素构造分离开来。对于内置类型,无需进行构造的调用一个方法,需要进行构造的类型调用另一个方法,这样提高了效率。
添加元素整体分为两种情况:空间充足与空间不充足。
如果空间足够,自然就很好解决了,直接将这些元素填充到后面的空间中,再改变finish的指向即可。
如果空间不够,这就要涉及到vector空间自增长的奥秘了。其实原理非常的简单,重新申请一块新空间,将原空间的数据copy进新空间,添加数据,销毁原空间。做法其实和array空间不够时一样,只是vector将其封装好了之后就不需要我们操心这么多了。当然,空间的增长也是需要一定的技巧的。不然我每次空间不够,都只申请比原空间大1的空间,这样反复的申请释放极大的浪费效率,不值当。在vector中,每次的自增长都会是原空间的两倍,甚至更多。
在进行插入操作的时候整体分为两种情况,空间充足和空间不充足的情况。空间足够的时候又分为两种。插入的元素需要进行构造和不需要进行构造的情况。
上面提到过,STL中空间的分配和元素的构造是分离开来的,当空间充足时,插入时,先将后面的数据移动到更后面,然后插入即可。(插入多个的时候略有不同)。直接填充,并没有构造,此时并没有调用需要进行构造的函数(uninitialized系列)可以看到,是否进行构造针对的是地址空间,而不是元素的值。当然,对于push_back()这个函数来说,它永远只可能出现空间不足或者填充的元素需要进行构造的情况。