C++ STL :Vector内存分配与释放
vector 可以容纳许多类型的数据,如若干个整数,所以称其为容器。
vector 是C++ STL的一个重要成员,使用它时需要包含头文件: #include<vector> 。
关于vector的使用,虽然可以动态的分配内存,但是稍不注意,就会落入内存陷阱中,无形中增大了程序的内存开销,导致程序崩溃。基于此,有必要梳理一下 C++ STL中的vector的内存分配与释放机制。
文章从“定义”、“添加”、“清空”三个部分来探究vector的内存分配和释放机制。
导读:
1. vector内存相关介绍
- size(),capacity(),push_back(),reserve(),resize(),clear(),swap()
- vector内存增长特点
2. vector定义与内存分配
- 类中定义 & 类外定义(包括全局、主函数、类成员函数、自定义函数等)
- 一维vector & 二维vector
- 空vector & 指定行vector & 指定行列vector
3. vector定义方式建议
4. vector清空元素与内存释放
- clear()
- swap()
以下是正文,测试代码会放在最后。
1. vector内存相关介绍
1.1 相关函数
(1)b.size():容器当前拥有的元素个数。
(2)b.capacity():容器在必须分配新存储空间之前可以存储的元素总数。
区别:创建完空vector后,其size和capacity都为0,但是向vector插入元素后,会发生变化,通常capacity大于等于size,这是vector内存增长机制决定的。
(3)b.push_back():在向量最后插入一个元素。 在调用push_back时,若当前容量已经不能够放入新的元素(capacity=size),那么vector会重新申请一块内存,把之前的内存里的元素拷贝到新的内存当中,然后把push_back的元素拷贝到新的内存中,最后要析构原有的vector并释放原有的内存。所以说这个过程的效率是极低的,为了避免频繁的分配内存, C++每次申请内存都会成倍的增长。
(4)b.reserve(a):容器预留空间a,但在空间内不真正创建元素对象(capacity=a,无size)。 所以在没有添加新的对象之前,不能引用容器内的元素。加入元素时,要调用push_back()/insert()函数, 值得一提的是,若添加元素没有超出预留,那么是不会对内存进行重新分配的 。此外,若加入元素超出了reserve()的值,是可以继续添加的,但是此时就会触发push_back()的空间预留机制。
(5)b.resize(a,0):改变容器的大小,且创建对象(指定或默认0,初始capacity = size = a)。 因此,调用这个函数之后,可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。此时再调用push_back()函数,是加在这个新的空间后面的。同样也会触发push_back()的空间预留机制。
容器类型提供resize操作来改变容器所包含的元素个数:如果当前的容器长度大于新的长度值,则该容器后部的元素会被删除;如果当前的容器长度小于新的长度值,则系统会在该容器后部添加新元素。
需要注意的是:resize操作可能会使 迭代器失效 ,对于所有的容器类型,如果resize操作压缩了容器,则指向已删除的元素的迭代器失效。
区别:
- reserve函数只有一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小, 第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。
- 当不超过预留空间时, reserve()不涉及内存的重新分配,resize()会涉及内存的重新分配 。但是如果是对空容器操作,那么二者看不出内在的区别。
- reserve()只修改capacity大小,不修改size大小,
- resize()既修改capacity大小,也修改size大小。
(6)b.clear():清空向量中的元素。 但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。一维vector.clear(),清空一维的元素,但是仍旧 保留着列的capacity ;二维vector.clear(),清空各行的列,并且回收列内存capacity,但是 保留行的capacity 。
(7)a.swap(b):将a中的元素和b中的元素进行整体性交换。 除此之外,①可以利用swap()方法 去除 vector多余的容量: vector<T>(x).swap(x) ;其中,x是当前要操作的容器,T是容器的类型。②利用swap()方法 清空 vector容器:当 swap() 成员方法用于清空 vector 容器时,可以套用如下的语法格式: vector<T>().swap(x) 。
1.2 vector内存增长策略和特点
(1)vector的容器的大小只可以增加,不可以减少。 当我们使用push_back() , insert() , emplace()等成员方法的时候,有可能会增加容量,但是我们使用 pop_back()、erase()、clear() 等方式的时候,并不会减少实际 的内存容量。只是可以删除容器里面的内容。
(2)vector 有一个机制是这样的,如果新加入一个元素,比如通过push_back(),但是size 大于了capacity,那么vector 就会重新找一块更大的地方再把数据放进去。重新分配的过程: 申请一块新的内存 > 拷贝数据 > 释放原内存 。
所以,使用vector容器的时候可以预先空间,把capacity定得够大,这样可以尽量避免重新分配vector 的内存,不增加程序的负担。即: reserve()预留空间+push_back()压入元素+size()控制读取 的策略,但是还是不推荐这么用,原因后面解释。
(3)综合来看,vector为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能难以接受。因此STL实现者在对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些。就是说,vector容器 预留了一些额外的存储区 ,用于存放新添加的元素,这样就不必为每个新元素重新分配整个容器的内存空间。
在调用push_back时,每次执行push_back操作,相当于底层的数组实现要重新分配大小;这种实现体现到vector实现就是每当push_back一个元素,都要重新分配一个大一个元素的存储,然后将原来的元素拷贝到新的存储,之后在拷贝push_back的元素,最后要析构原有的vector并释放原有的内存。
2. vector定义与内存分配
2.1 类中定义 & 类外定义
vector在类中私有/公有成员中的定义方式居然和全局、函数内的定义方式还有所区别,具体原因也没了解到(和默认构造函数有关),总之用这玩意儿到处都是坑,吐。
(1)类外定义(圆括号)
一维vector
vector<int>a(10);
二维vector
vector<vector<int>>c(5, vector<int>(10));
以上定义都是有初始值的,所以可以 直接用下标访问 。举例的是已知一些行列信息的定义方式,其余的定义方式在下面说,差不多。
(2)类中定义{花括号}
不能使用以上类外的两种定义方式。
一维vector:无法指定列长,可以用 花括号{ } 初始化列元素。
vector<int>a1{ 1,2,3 };
二维vector:可以用 花括号{ }指定行数 ,无法指定列长,但可以用 花括号{ } 初始化列元素。
vector<vector<int>>c{ N };
vector<vector<int>>d1{ N ,vector<int> {1,2,3} };
只要vector中没有定义列长,那么就是个空的容器 。具体细节见下代码:
class Vector_define
private:
int i, j;
/*一维vector*/
//vector<int>a(N); /*在自定义类中无法对vector实现圆括号()初始化长度的定义*/
vector<int>a{ N }; /*此种花括号{}的初始化可以定义。作用:只是输入一个元素,而非定义长度*/
vector<int>a1{ 1,2,3 }; /*支持直接输入元素*/
vector<int>b; /*正常定义,空的,无长度*/
/*二维vector*/
//vector<vector<int>>c(N, vector<int>(T)); /*该种定义同一维,无效定义*/
vector<vector<int>>c{ N }; /*二维vector该种花括号{}定义有效。作用:占行,而非输入元素,这和一维不一样*/
//vector<vector<int>>c{ 1,2,3 }; /*无效定义*/
vector<vector<int>>d{ N ,vector<int> {T} }; /*该种定义,支持行定义,不支持列定义,但支持行添加*/
vector<vector<int>>d1{ N ,vector<int> {1,2,3} }; /*支持行直接输入元素,且是为每一行都插入*/
vector<vector<int>>e; /*正常定义,空的,无长度*/
3. vector定义方式建议
经过以上的一些探索,在这对不同vector定义方式的内存变化再做出一些分析和建议。
(1)一维vector
①一开始知道列长。
vector<int>a(10);
②一开始不知道列长,resize()策略。
vector<int>c;
c.resize(25);
优点:按需分配,有初始值,可以用下标直接改值。
③一开始不知道列长,reserve()+push_back()策略。
vector<int>a;
a.reserve(25);
for (i = 0; i < 25; i++)
a.push_back(i + 1);
缺点:不建议用,虽然也是按需分配,但是由于push_back()可能带来内存溢出。
(2)二维vector(以类中定义为例)
①一开始知道行、列信息的,resize()策略。
vector<vector<int>>h(5);
for (i = 0; i < h.size(); i++)
h[i].resize(10);
当对空vector采取resize(), capacity=size ,但如果是对一个已有元素的vector进行resize(),或者是又对resize后的vector,push_back了一些元素,那么就会触发 内存预留 机制,所以如果要想 去除多余 的空间,可以采用如下方法:
vector<int>(h).swap(h)
优点:按需分配,有初始值,可以用下标直接改值。
②一开始知道行、列信息的,reserve()+push_back()策略。
不推荐!!!原因如下:
1)因为但凡用到push_back(),如果 忘记清空 ,稍不注意,在某些循环中,循环一次就压入一堆元素,不光影响数据的使用,而且还会不停扩张内存,更严重的是当用完预留的内存后,更是成倍的开辟内存空间,直逼程序崩溃。
2)即便是记得使用清空的策略去除无关的元素,也容易错误的使用清空方式。比如:
vector<vector<int>> b{ N };
for (i = 0; i < N; i++)
b[i].reserve(T);
该二维vector在定义时,已经指定了行数,所以“b.clear()”的行为是错误的,而“b[0],clear()”才是正确的,也即只能清空列。
又或者你想这样定义( 错误! ):
vector<vector<int>> e;
e.reserve(10);
for (i = 0; i < N; i++)
b[i].reserve(10);
看着没问题,实际是个错误的定义。 若一开始没有定义行,那么是无法通过reserve()固定行的;只能通过resize()的方式。
总结起来就是,这种策略的定义方式,特别容易出错!
③其他的情况暂且不论,总之能用resize()就用,能不用push_back()就不用,一旦用了,根据情况,需要清空的一定要正确及时的清空,避免内存损耗和溢出!
4. vector清空元素与内存释放
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。 所有内存空间是在vector析构时候才能被系统回收。 empty()用来检测容器是否为空的,clear()可以清空所有元素。但是 即使clear(),vector所占用的内存空间依然如故 ,无法保证内存的回收。
就像前面所说的,vector的内存空间是只增加不减少的,我们常用的操作clear()和erase(),实际上只是减少了size(),清除了数据,并不会减少capacity,所以内存空间没有减少。 那么如何释放内存空间呢,正确的做法是swap()操作。
swap交换技巧实现内存释放思想:vector()使用vector的默认构造函数建立临时vector对象,再在该临时对象上调用swap成员,swap调用之后原来vector占用的空间就等于一个默认构造的对象的大小,临时对象就具有原来对象v的大小,而该临时对象随即就会被析构,从而其占用的空间也被释放。
当 swap() 成员方法用于清空 vector 容器时,可以套用如下的语法格式:
vector<T>().swap(x);
这里没有为 vector<T>() 表达式传递任何参数。这意味着,此表达式将调用 vector 模板类的默认构造函数,而不再是复制构造函数。也就是说,此格式会先生成一个空的 vector 容器,再借助 swap() 方法将空容器交换给 x,从而达到 清空 x 的目的。
完整测试代码:
#include<iostream>
using namespace std;
#include<vector>
#define N 20
#define T 10
/*类Vector_define:探究在类中对vector进行定义的特点*/
class Vector_define
private:
int i, j;
/*一维vector*/
//vector<int>a(N); /*在自定义类中无法对vector实现圆括号()初始化长度的定义*/
vector<int>a{ N }; /*此种花括号{}的初始化可以定义。作用:只是输入一个元素,而非定义长度*/
vector<int>a1{ 1,2,3 }; /*支持直接输入元素*/
vector<int>b; /*正常定义,空的,无长度*/
/*二维vector*/
//vector<vector<int>>c(N, vector<int>(T)); /*该种定义同一维,无效定义*/
vector<vector<int>>c{ N }; /*二维vector该种花括号{}定义有效。作用:占行,而非输入元素,这和一维不一样*/
//vector<vector<int>>c{ 1,2,3 }; /*无效定义*/
vector<vector<int>>d{ N ,vector<int> {T} }; /*该种定义,支持行定义,不支持列定义,但支持行添加*/
vector<vector<int>>d1{ N ,vector<int> {1,2,3} }; /*支持行直接输入元素,且是为每一行都插入*/
vector<vector<int>>e; /*正常定义,空的,无长度*/
public:
void test1()
/*teat a*/
cout << "a.size()=" << a.size() << endl; /* a.size()=1 ,可知类中花括号只是添加了元素而已*/
for (i = 0; i < a.size(); i++)
cout << a[i] << " "; /* 只输出 20 ,也即只有一个元素*/
cout << endl << endl;
/*teat b*/
cout << "b.size()=" << b.size() << endl; /* b.size()=0 ,没有元素,正常*/
/*for (i = 0; i < a.size(); i++)
cout << b[i] << " "; // 报错:out of range! 空的容器,没有长度,没有东西输出!
cout << endl ;*/
cout << endl;
/*teat c*/
cout << "c.size()=" << c.size() << endl; /* c.size()=20,可知二维vector支持花括号{}定义行数 */
for (i = 0; i < c.size(); i++)
for (j = 0; j < c[i].size(); j++)
cout << c[i][j] << " "; /* 不报错,有行没列,输出不了什么, */
cout << endl;
cout << endl;
/*teat d*/
cout << "d.size()=" << d.size() << endl; /* d.size()=20,花括号{}行定义仍旧有效 */
for (i = 0; i < d.size(); i++)
cout << "d[" << i << "].size()=" << d[i].size() << " ";
} /* d[i].size()=1,可知花括号{}列长定义无效 */
cout << endl;
for (i = 0; i < d.size(); i++)
for (j = 0; j < d[i].size(); j++)
cout << d[i][j] << " "; /* d[i][j]=T,可知花括号{}列长定义无效,只是添加了一个元素 */
cout << endl;
cout << endl;
/*teat e*/
cout << "e.size()=" << e.size() << endl; /* e.size()=0,行0,列空 */
for (i = 0; i < e.size(); i++)
cout << "e[" << i << "].size()=" << e[i].size() << " ";
} /* 列空 */
cout << endl;
for (i = 0; i < e.size(); i++)
for (j = 0; j < e[i].size(); j++)
cout << e[i][j] << " "; /* 容器空 */
cout << endl;
cout << endl;
/*类Vector_memory:探究在类中定义的vector的内存特点*/
class Vector_memory
private:
int i, j;
/*一维*/
vector<int>a;
vector<int>b{ 1,2,3 };
vector<int>c;
vector<int>d;
/*二维*/
vector<vector<int>>e;
vector<vector<int>>f{ N };
vector<vector<int>>g{ N,vector<int>{1,2,3} };
public:
void test2()
/*一维内存*/
cout << "a.capacity()=" << a.capacity() << endl; /* a.capacity()=0 */
cout << "a.size()=" << a.size() << endl << endl; /* a.capacity()=0 */
cout << "b.capacity()=" << b.capacity() << endl; /* a.capacity()=3,不会预留空间*/
cout << "b.size()=" << b.size() << endl << endl; /* a.capacity()=3 */
//①往 a 中添加元素:研究 pushback() 对内存的改变
for (i = 0; i < N; i++)
a.push_back(i + 1);
cout << "a.capacity()=" << a.capacity() << endl; /* a.capacity()=28,可知pushback()会预留部分空间*/
cout << "a.size()=" << a.size() << endl << endl; /* a.size()=20,实际空间等于元素个数*/
//②给 a 重设长度:研究 resize() 对内存的改变
a.resize(30);
cout << "a.capacity()=" << a.capacity() << endl; /* a.capacity()=42,可知resize()会对缓存区进行重新分配和空间预留*/
cout << "a.size()=" << a.size() << endl << endl; /* a.capacity()=30,可知resize()会产生初始值*/
//③给 c 重设长度:研究 resize() 对内存的改变
c.resize(25);
cout << "c.capacity()=" << c.capacity() << endl; /* c.capacity()=25,可以发现:当对空容器resize(),不会预留空间*/
cout << "c.size()=" << c.size() << endl << endl; /* c.capacity()=25*/
//④给 a 预设长度:研究 reserve() 对内存的改变
a.reserve(50);
cout << "a.capacity()=" << a.capacity() << endl; /* a.capacity()=50,可知reserve()不会对缓存区进行重新分配和空间预留*/
cout << "a.size()=" << a.size() << endl << endl; /* a.size()=30,可知reserve()不会产生初始值*/
//⑤给 a 添加超出它预设capcity的元素:研究 capcity 的变化
for (i = 0; i < 25; i++)
a.push_back(i + 1);
cout << "a.capacity()=" << a.capacity() << endl; /* a.capacity()=75,可知当元素超出预设的capcity,就会触发空间预留*/
cout << "a.size()=" << a.size() << endl << endl; /* a.size()=55*/
/*二维内存*/
cout << "e.capacity()=" << e.capacity() << endl; /* e.capacity()=0,列的capcity报错 */
cout << "f.capacity()=" << f.capacity() << endl; /* f.capacity()=20 */
cout << "f[0].capacity()=" << f[0].capacity() << endl; /* f[0].capacity()=0,指定行则有列的capcity */
cout << "g.capacity()=" << g.capacity() << endl; /* g.capacity()=20 */
cout << "g[2].capacity()=" << g[2].capacity() << endl; /* g[2].capacity()=3,可知每行都被插入了相同的元素 */
/*经过一维vector的探究,选出一些适合二维的节省资源的定义方式!*/
//①对于可求得固定行、列信息的二维vector,定义如下:
vector<vector<int>>h{ N };
for (i = 0; i < h.size(); i++)
h[i].reserve(T);
cout << "h.capacity()=" << h.capacity() << endl; /* h.capacity()=20 */
cout << "h[0].capacity()=" << h[0].capacity() << endl; /* h[0].capacity()=10 */
cout << endl;
//②对于可求得固定行信息,但是列长未知的二维vector,定义如下:
vector<vector<int>>p{ N };
/*通过push_back压入元素,会有预留空间*/
for (i = 0; i < p.size(); i++)
for (j = 0; j < i + 1; j++)
p[i].push_back(j + 1);
/*看看预留空间:发现确实有预留现象!*/
for (i = 0; i < p.size(); i++)
cout << "p[" << i << "].size()=" << p[i].size() << endl;
cout << "p[" << i << "].capacity()=" << p[i].capacity() << endl;
cout << endl;
/*有没有办法切去多余的空间?——利用swap()方法去除vector多余的容量*/
vector<vector<int>>(p).swap(p);
for (i = 0; i < p.size(); i++)
cout << "p[" << i << "].size()=" << p[i].size() << endl;
cout << "p[" << i << "].capacity()=" << p[i].capacity() << endl;
cout << endl;
//③对于无任何信息的二维vector,直接定义空的就行。
vector<vector<int>>t;
/*注意:若一开始没有定义行,那么是无法通过reserve()固定行的;
只能通过resize()的方式。
/*类Vector_release:探究如何彻底释放在类中定义的vector的内存*/
class Vector_release
private:
int i, j;
public:
vector<vector<int>> a;
vector<vector<int>> b{ N };
vector<vector<int>> c;
vector<int>d{ 1,2,3 };
void define()
//①经过以上测试的探索,我们可以有新的定义方式:用 行、列resize()+swap()
a.resize(N);
for (i = 0; i < a.size(); i++)
a[i].resize(i + 1);
vector<vector<int>>(a).swap(a); /*切除多余空间*/
for (i = 0; i < a.size(); i++)
for (j = 0; j < a[i].size(); j++)
cout << a[i][j] << " ";
cout << endl;
cout << endl;
/*cout << "a.size()" << a.size() << endl;
for (i = 0; i < a.size(); i++)
cout << "a[" << i << "].capacity()=" << a[i].capacity() << endl;
cout << endl;*/
//②用 指定行、列reserve()+push_back()定义
for (i = 0; i < N; i++)
b[i].reserve(T);
for (i = 0; i < N; i++)
for (j = 0; j < T; j++)
b[i].push_back(1);
cout << "bbbbb" << endl;
for (i = 0; i < b.size(); i++)
for (j = 0; j < b[i].size(); j++)
cout << b[i][j] << " ";
cout << endl;
cout << endl;
//③copy一个
c = a;
//}/*可以清空列元素,但是列的capacity还在。但至少可以保证循环时的push_back()不会溢出内存*/
for (i = 0; i < b.size(); i++)
vector<int>().swap(b[i]);
}/*可以清空列元素以及列的capacity。*/
//c.clear();/*二维vector.clear(),可以彻底清除列的capacity,但是保留着行的capacity*/
vector<vector<int>>().swap(c);/*利用swap()彻底清除capacity*/
d.clear();/*一维vector.clear(),保留着列的capacity*/
int main()
Vector_define v1;
v1.test1();
Vector_memory v2;
v2.test2();
Vector_release v3;
for (int it = 0; it < 4; it++)
cout << "第" << it << "次:" << endl;
v3.define();
v3.clean();
for (int i = 0; i < v3.a.size(); i++)
cout << "a[i].capacity[" << i << "]=" << v3.a[i].capacity() << " ";
cout << endl << endl;
for (int i = 0; i < v3.b.size(); i++)
cout << "b[i].capacity[" << i << "]=" << v3.b[i].capacity() << " ";
cout << endl << endl;
cout << "c.capacity()=" << v3.c.capacity() << endl;
for (int i = 0; i < v3.c.size(); i++)
cout << "c[i].capacity[" << i << "]=" << v3.c[i].capacity() << " ";
cout << endl;
cout << "d.capacity()=" << v3.d.capacity() << endl;