概述
前言
在本小节中,我们将接着上一下节,继续分析关于vector
的内存控制,及一些常用操作部分。
vector
的内存控制
引入
在上一节中我们给出了一个简单的例子,借助这个例子已经大约明白了vector
管理内存的机制。接下来,我们就来看看push_back
这些操作。
push_back
void push_back(const T& x) {
/* 判断是否到vector最大容量
* 若没有,则构造该对象
* 若达到了,则调用`insert_aux`函数
*/
if (finish != end_of_storage) {
construct(finish, x);
++finish;
}
else
insert_aux(end(), x);
}
事实上,发生容量增倍的操作在insert_aux
中。
insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
/* 若还有空间可用,则直接使用
* 如果没有,则扩充空间
*/
if (finish != end_of_storage) {
//构造一个新的元素,并以vector最后一个元素作为初值
//finish迭代器指向的位置是没有元素的
//不要忘了[first, finish),在容器中都是这样
construct(finish, *(finish - 1));
++finish;
T x_copy = x;
/* 这步操作实现的是将[position, finish -2)的元素从尾到头复制到从finish-1-1指向的位置(依次向后移)
* 比如 1 2 3 4 5 5
* 结果就是 1 2 2 3 4 5
* 然后再将position指向的位置赋成x
*/
copy_backward(position, finish - 2, finish - 1);
//赋值
*position = x_copy;
}
else {
/*进行到这里,代表容量不够了 进行扩容 */
//此时的size()大小应该与capacity()大小相等
const size_type old_size = size();
/* 这里考虑到了vector容量为0的情况
* 若不为0,则扩充为原来的2倍
*/
const size_type len = old_size != 0 ? 2 * old_size : 1;
/* 注意vector并不是在原空间的基础上进行扩充
* 而是调用空间配置器另外申请一块新的空间
* 这是为了防止原有空间无法提供这么大的空间
*/
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
//将原vector的元素拷贝到新的vector中
new_finish = uninitialized_copy(start, position, new_start);
//在新的vector中插入x
construct(new_finish, x);
++new_finish;
//将插入点position之后元素也拷贝过来
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
//以下是异常情况
//若发生异常,则销毁拷贝过去的元素
//最后将申请的空间释放
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
/* 一切大功告成之后,将原空间进行销毁
* 并把start、finish、end_of_storage这几个迭代器指向新的空间
*/
destroy(begin(), end());
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
可能你还有一些疑问,那就是为什么insert_aux
中,当有容量可用时,为什么不像push_back
那样直接构造元素,而要调用copy_backward
。这是因为,insert_aux
也可能会被insert
调用,而insert
是指定位置进行插入的。
并且当空间分配成功后,拷贝旧空间的元素时,也要将position之后的元素拷贝到新空间中,这对于push_back
操作其实是不必要的,但是同样,insert
操作可能会遇到这种情况。
接下来,我们再看一看vector
的insert
操作(STL规定插入操作是插入到position的前一个位置)
insert
insert
函数其实有众多的重载版本,大致分为:
- 指定位置插入一个元素
- 指定位置插入多个元素
- 以迭代器的范围插入
//指定位置插入x
iterator insert(iterator position, const T& x) {
size_type n = position - begin();
if (finish != end_of_storage && position == end()) {
construct(finish, x);
++finish;
}
else
insert_aux(position, x);
return begin() + n;
}
iterator insert(iterator position) { return insert(position, T()); }
//迭代器范围插入
#ifdef __STL_MEMBER_TEMPLATES
template <class InputIterator>
void insert(iterator position, InputIterator first, InputIterator last) {
range_insert(position, first, last, iterator_category(first));
}
#else /* __STL_MEMBER_TEMPLATES */
void insert(iterator position,
const_iterator first, const_iterator last);
#endif /* __STL_MEMBER_TEMPLATES */
//在pos前插入n个x
void insert (iterator pos, size_type n, const T& x);
void insert (iterator pos, int n, const T& x) {
insert(pos, (size_type) n, x);
}
void insert (iterator pos, long n, const T& x) {
insert(pos, (size_type) n, x);
}
接下来,我们来分析这三种情况:
1. 指定位置插入
iterator insert(iterator position, const T& x) {
size_type n = position - begin();
/* 如果此时有足够的空间并且插入的元素在末端
* 就直接在末端插入新元素
* 如果没有足够空间或插入的元素不在尾部
* 则调用insert_aux进行插入(插入的元素不在尾部会走if分支,容量不够走else分支)
*/
if (finish != end_of_storage && position == end()) {
construct(finish, x);
++finish;
}
else
insert_aux(position, x);
//返回指向插入元素的迭代器
return begin() + n;
}
2. 范围插入
根据条件编译分不同的函数,但是实现都是一样的,range_insert
和下面这个insert
的代码一致,就拿其中一个分析就行了。
template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position,
const_iterator first,
const_iterator last) {
if (first != last) {
size_type n = 0;
//计算出两个迭代器之间的距离(即元素个数)
distance(first, last, n);
//容量够用的情况
if (size_type(end_of_storage - finish) >= n) {
const size_type elems_after = finish - position;
iterator old_finish = finish;
/* 若插入点之后的元素个数大于新增元素个数
* 那么我们就可以腾出n个位置给新增元素
* 如果插入点之后的元素个数小于新增元素个数
* 则先将[first + elems_after, last)的元素拷到finish之后
* 然后将[position, old_finish)的元素拷贝到finish之后,腾出elems_after个位置
* 最后将[first, first + elems_after)的元素拷贝到腾出的位置中
*/
if (elems_after > n) {
//将[finish-n, finish)范围内的元素移动到finish之后
//目的在于腾出n个位置给新元素
uninitialized_copy(finish - n, finish, finish);
finish += n;
//将[position, old_finish - n)的元素依次后移
copy_backward(position, old_finish - n, old_finish);
//将[first, last)拷贝进position指向的位置
copy(first, last, position);
}
else {
//这个插入操作其实是分了两部分的
//其中一部分是将[first+elems_after, last)的元素拷贝到finish之后,尾部假定为new_finish
//而[first, first+elems_after)这一部分则先让原来的元素腾到new_finish之后,再复制到腾出的地方
uninitialized_copy(first + elems_after, last, finish);
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
copy(first, first + elems_after, position);
}
}
else {
//以下是空间不足需要重新分配的情况
const size_type old_size = size();
const size_type
//此时并没有直接分配old_size的两倍
//这是考虑到分了old_size的两倍之后,n过于大,容量还是不够的情况
//所以取两者的最大值
//再不济,也要把插入n个元素装下
len = old_size + max(old_size, n);
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
/* 因为是插入到position之前
* 所以需要分成三部将元素拷贝到新空间中
* 第一部分:[start, position)
* 第二部分:[first, last)
* 第三部分:[position, finish)
*/
new_finish = uninitialized_copy(start, position, new_start);
new_finish = uninitialized_copy(first, last, new_finish);
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
//异常处理
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
//释放原空间,并调整迭代器
destroy(start, finish);
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}
容量够用的情况中又分成的elems_after > n或者elems_after < n这两种情况可能有点不好理解,最好自己画一下图有助于理解。
3. 插入n个元素
根据了n的类型不同进行了重载,不过最后都将n强转成size_type
类型进行处理。
该函数和迭代器范围插入很相似,代码的逻辑几乎一模一样,除了迭代器范围插入时,插入新元素是调用copy
拷贝,而插入n个元素这类是调用fill
之外,并没有什么不一样,这里就不再贴代码占篇幅了。
clear
vector
中的clear
函数并不负责回收vector
的空间,而是负责销毁vector
所存的元素。
它的源码如下:
void clear() { erase(begin(), end());}
其实就是调用了erase
函数。那我们接着来看erase
函数。它有重载的版本。
//删除position迭代器指向的元素
iterator erase(iterator position) {
/* 若position指向的不是最后一个元素
* 那么直接将后面的元素向前移动就完事
* 如果是最后一个,那就更简单了,直接将finish指向该元素,然后删除
*/
if (position + 1 != end())
copy(position + 1, finish, position);
--finish;
destroy(finish);
return position;
}
//删除[first, last)范围内的元素
iterator erase(iterator first, iterator last)
{
//将last之后的元素拷贝到first的位置,覆盖需要删除的元素
iterator i = copy(last, finish, first);
//当last-first的值(需要删除的元素个数)大于finish-last的值时,需要删除的元素没有被覆盖完,则调用destroy进行析构
destroy(i, finish);
//调整finish
finish = finish - (last - first);
return first;
}
其他操作
关于vector
的操作确实挺多,以上也只列举了一些比较重要的。
下面我们再看几个操作符重载的函数以及reserve
和resize
函数。
重载[]
reference operator[](size_type n) { return *(begin() + n); }
重载==
template <class T, class Alloc>
inline bool operator==(const vector<T, Alloc>& x, const vector<T, Alloc>& y) {
return x.size() == y.size() && equal(x.begin(), x.end(), y.begin());
}
可以看到两个vector
容器是否相等需要满足两个条件:
- 元素个数相等
- 元素的值相等
重载=
template <class T, class Alloc>
vector<T, Alloc>& vector<T, Alloc>::operator=(const vector<T, Alloc>& x) {
//如果自己对自己赋值,那直接返回就行了
if (&x != this) {
/* 判断自身的容量大小是否能存入x
* 如果不能,分配一个跟x大小相同的空间,并将x拷贝进去
* 然后销毁原空间
* 再把迭代器指向新的空间
*/
if (x.size() > capacity()) {
iterator tmp = allocate_and_copy(x.end() - x.begin(),
x.begin(), x.end());
destroy(start, finish);
deallocate();
start = tmp;
end_of_storage = start + (x.end() - x.begin());
}
/* 现有元素足够容下x
* 直接将x的元素拷贝过来
* 将没有被覆盖的元素析构掉
*/
else if (size() >= x.size()) {
iterator i = copy(x.begin(), x.end(), begin());
destroy(i, finish);
}
/* 这种情况是现有元素的个数容不小x
* 但是总容量可以容下x
* 所以就需要把现有的元素全部覆盖了,然后把x剩下的元素拷贝到未使用的空间中去
*/
else {
copy(x.begin(), x.begin() + size(), start);
uninitialized_copy(x.begin() + size(), x.end(), finish);
}
//最后统一调整finish
finish = start + x.size();
}
//返回赋值后的vector
return *this;
}
重载赋值操作符可能要稍微麻烦一点,不过比起insert
之类的还是比较简单了。
最后再介绍两个函数reserve
、resize
reserve
该函数的作用是将总容量设置为n
void reserve(size_type n) {
if (capacity() < n) {
const size_type old_size = size();
//分配n大小的空间,将原来的元素拷贝过去
iterator tmp = allocate_and_copy(n, start, finish);
//删除原空间的元素
destroy(start, finish);
//释放原空间
deallocate();
//调整迭代器
start = tmp;
finish = tmp + old_size;
end_of_storage = start + n;
}
}
resize
该函数的作用就是调整vector
的大小。
void resize(size_type new_size, const T& x)
{
/* 如果要求调整到的大小比原来的大小小
* 则需要将超出的部分删掉
* 否则将填入新的元素,初值为x
*/
if(new_size < size())
erase(begin() + new_size, end());
else
insert(end(), new_size - size(), x);
}
小结
在本小节中我们分析了push_back
、insert
及一些操作符还有reserve
及resize
等函数。还有一些其他的操作不需要一一列举,感兴趣的可以自己去看代码,没有特别的复杂的了。
还需要特别注意的一点就是关于迭代器的失效问题,如果vector
扩容了,那么会导致原来的迭代器都会失效,了解了vector
源码之后,对这些特性应该也能理解。
关于vector
的分析就到此结束,相信你对vector
的内部机制有了更深刻的了解。接下来是list
容器。
最后
以上就是简单哈密瓜为你收集整理的SGISTL源码探究-vector容器(下)的全部内容,希望文章能够帮你解决SGISTL源码探究-vector容器(下)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复