1.vector内存分配机制
C++中vector的一个特点是: 内存空间只会增长,不会减小。即为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都挨着前一个元素存储。设想,如果每次vector添加一个新元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能就会非常慢。
所以,实际上分配时其容量要比当前所需容量更多,即vector预留了一些额外的存储区,这样就不必单独为每个新元素重新分配内存空间,减少开销。 另外,在vector中内存只增不减体现在 - 比如首先分配了10000个字节,然后erase掉后面9999个,留下一个有效元素,但是实际上内存占用仍然为10000个,所有内存空间是在vector析构的时候才能被系统回收。所以,即使使用clear
,vector所占用的内存空间依然如故,无法保证内存的回收。
当然,对于数据量很小的vector,完全没有必要进行主动释放,就比如200 * 200的网格计算,就是没有必要的,但是如果是1000 * 1000,那么就是前者的25倍了,这时就需要进行主动内存释放了。
另外,既然要进行内存释放,我们不得不掌握size()和capacity()方法的区别,前者是实际的vector元素个数,后者是实际占用内存的个数,一般来说,capacity()是大于或等于size()的。
通过分析 vector 容器的源代码不难发现,它就是使用 3 个迭代器(可以理解成指针)来表示的:
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
...
protected:
pointer _Myfirst;
pointer _Mylast;
pointer _Myend;
};
如图 1 所示,通过这 3 个迭代器,就可以表示出一个已容纳 2 个元素,容量为 5 的 vector 容器。
在此基础上,将 3 个迭代器两两结合,还可以表达不同的含义,例如:
_Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
_Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
_Myfirst 和 _Myend 可以用表示 vector 容器的容量。
对于空的 vector 容器,由于没有任何元素的空间分配,因此 _Myfirst、_Mylast 和 _Myend 均为 null。
vector<int> nums;
nums.resize(10);
nums.reserve(20);
vector扩容机制三步走另外需要指明的是,当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:
完全弃用现有的内存空间,重新申请更大的内存空间
;将旧内存空间中的数据,按原有顺序移动到新的内存空间中
;最后将旧的内存空间释放
。
这也就解释了,为什么 vector 容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效的原因。
由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。
vector 容器扩容时,不同的编译器申请更多内存空间的量是不同的。以 VS 为例,它会扩容现有容器容量的 50%,根据等比数列性质可知,这样做可以复用前面的内存空间。
即1+2+4 < 2 * 4=8;而1+1.5+2.25=4.75>2.25 * 1.5=3.375。
释放方法
vector<int> nums;
nums.push_back(1);
nums.push_back(1);
nums.push_back(2);
nums.push_back(2);
vector<int>().swap(nums);
或者nums.swap(vector<int> ())
或者是加括号,人为得为其限定作用域,在离开作用域后自动析构
{
std::vector<int> tmp = nums;
nums.swap(tmp);
}
综合应用:
int main(){
vector<int> nums;
cout << nums.size() << " " << nums.capacity() << endl;
for (int i = 0; i < 10; i++) {
nums.push_back(i);
cout << nums.size() << " " << nums.capacity() << endl;
}
cout << nums.size() << " "<< nums.capacity() << endl;
nums.clear();
cout << nums.size() << " " << nums.capacity() << endl;
nums.swap(vector<int>());
cout << nums.size() << " " << nums.capacity() << endl;
nums.reserve(15);
nums.resize(2);
cout << nums.size() << " " << nums.capacity() << endl;
return 0;
}
输出结果
2.vector中不是基础数据成员
当时如果nums是一个类
的成员,不能把vector.swap(nums)写进类的析构函数中,否则会导致double free or corruption (fasttop)的错误,原因可能是重复释放内存。标准解决方法如下:
template < class T >
void ClearVector( vector< T >& vt )
{
vector< T > vtTemp;
veTemp.swap( vt );
}
如果vector中存放的是指针
,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:
#include <vector>
using namespace std;
vector<void *> v;
每次new之后调用v.push_back()该指针,在程序退出或者根据需要,用以下代码进行内存的释放:
for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++)
if (NULL != *it)
{
delete *it;
*it = NULL;
}
v.clear();
那么问题来了,假如在vector中存放含有指针的结构体呢
。
这是一个没有析构函数的版本
。
struct havep {
int a;
int *p;
havep() {
p = new int(777);
}
};
int main(){
vector<havep> test;
havep a1;
havep a2;
test.push_back(a1);
test.push_back(a2);
test.clear();
system("pause");
}
以刚才的形式向容器中直接放结构体,发生了某种意义上的浅拷贝。
继续运行,clear后,a1,a2中的p指向内容不被改变。
添加析构函数:
~havep() {
delete p;
p = nullptr;
}
再次运行,clear后报错。所以这种直接放结构体的形式比较危险,浅拷贝时往往会发生未知错误。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)