动态表和C++ vector
动态表和C++ vector
最近课上刚刚学了可以根据表中元素的插入和删除动态调整表大小的动态表(dynamic table),就想看一下它有什么实际的应用,第一个想起来的就是C++的vector,直觉中它是最符合动态表特性的了(预先不需要声明大小,当然你要是想声明也没问题,动态插入和删除)。但是下面这篇文章说明了vector不算是一个完全的动态表,因为它的内存占用随着元素的插入和删除是只增加不释放的
来源:https://blog.csdn.net/qq_30835655/article/details/60762196
最近开始更加深入的学习C++,发现了很多以前没注意到但是很重要的知识点。这篇文章主要说vector内存机制和效率问题。
- vector内存增长
vector所有的内存相关问题都可以归结于它的内存增长策略。vector有一个特点就是:内存空间只会增长不会减少。vector有两个函数,一个是capacity(),返回对象缓冲区(vector维护的内存空间)实际申请的空间大小,另一个size(),返回当前对象缓冲区存储数据的个数。对于vector来说,capacity是永远大于等于size的,档capacity和size相等时,vector就会扩容,capacity变大。比如说vector最常用的push_back操作,它的整个过程是怎么一个机制呢?这个问题经常在面试中出现。
这个问题其实很简单,在调用push_back时,若当前容量已经不能够放入心得元素(capacity=size),那么vector会重新申请一块内存,把之前的内存里的元素拷贝到新的内存当中,然后把push_back的元素拷贝到新的内存中,最后要析构原有的vector并释放原有的内存。所以说这个过程的效率是极低的,为了避免频繁的分配内存,C++每次申请内存都会成倍的增长,例如之前是4,那么重新申请后就是8,以此类推。当然不一定是成倍增长,比如在我的编译器环境下实测是0.5倍增长,之前是4,重新申请后就是6。
接下来我们通过一个简单的程序实例来观察这个过程。
编译环境:visual studio 2015
#include<vector>
#include<iostream>
using namespace std;
class Point
{
public:
Point()
{
cout << "construction" << endl;
}
Point(const Point& p)
{
cout << "copy construction" << endl;
}
~Point()
{
cout << "destruction" << endl;
}
};
int main()
{
Point test[10];
cout << "**************************" << endl;
vector<Point> arr;
for (int i = 0; i < 10; i++)
{
arr.push_back(test[i]);
cout << "capacity=" << arr.capacity() << ",size=" << arr.size() << endl;
cout << "--------------------------" << endl;
}
system("pause");
}
程序运行结果
construction
construction
construction
construction
construction
construction
construction
construction
construction
construction
**************************
copy construction
capacity=1,size=1
--------------------------
copy construction
destruction
copy construction
capacity=2,size=2
--------------------------
copy construction
copy construction
destruction
destruction
copy construction
capacity=3,size=3
--------------------------
copy construction
copy construction
copy construction
destruction
destruction
destruction
copy construction
capacity=4,size=4
--------------------------
copy construction
copy construction
copy construction
copy construction
destruction
destruction
destruction
destruction
copy construction
capacity=6,size=5
--------------------------
copy construction
capacity=6,size=6
--------------------------
copy construction
copy construction
copy construction
copy construction
copy construction
copy construction
destruction
destruction
destruction
destruction
destruction
destruction
copy construction
capacity=9,size=7
--------------------------
copy construction
capacity=9,size=8
--------------------------
copy construction
capacity=9,size=9
--------------------------
copy construction
copy construction
copy construction
copy construction
copy construction
copy construction
copy construction
copy construction
copy construction
destruction
destruction
destruction
destruction
destruction
destruction
destruction
destruction
destruction
copy construction
capacity=13,size=10
--------------------------程序运行结果简单明了,但是有一点令人很不解,vector原空间的析构居然是在新元素的push_back前面发生,与网上查询资料有所不同。有待考证,希望阅读者可以在自己的环境下测试,观察结果。
- 内存释放
就像前面所说的,vector的内存空间是只增加不减少的,我们常用的操作clear()和erase(),实际上只是减少了size(),清除了数据,并不会减少capacity,所以内存空间没有减少。那么如何释放内存空间呢,正确的做法是swap()操作。标准模板如下
template < class T >
void ClearVector( vector< T >& vt )
{
vector< T > vtTemp;
veTemp.swap( vt );
}
也可以简单使用以下操作
vector().swap(pointVec); //或者pointVec.swap(vector ())swap交换技巧实现内存释放思想:vector()使用vector的默认构造函数建立临时vector对象,再在该临时对象上调用swap成员,swap调用之后原来vector占用的空间就等于一个默认构造的对象的大小,临时对象就具有原来对象v的大小,而该临时对象随即就会被析构,从而其占用的空间也被释放。
std::vector().swap(X)
作用相当于:
{
std::vector temp(X);
temp.swap(X);
}交换之后,temp会被析构。
3.总结
由上可见,vector虽然是动态数组,但是本质上和数组没什么区别,频繁的销毁新建,效率很低,所以正确的做法是新建vector的时候初始化一个合适的大小(笑),回到了数组的老路上。不过之后可以动态变化还是很方便,而且还有很多好用的函数。
————————————————
版权声明:本文为CSDN博主「尹小贱」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_30835655/article/details/60762196
所以vector的大小只能增长、不会减小,要释放vector占用的内存空间需要借助交换vector内容的函数swap,与空的代码块中的对象或者匿名对象进行交换会自动销毁临时对象,并不完全符合动态表的要求。当然,如果不手动使用swap进行回收空间,vector对象在对应的函数执行完成后也会回收的(参见上面的temp对象),但是在一些递归过程中这样很可能会爆栈,或者其他内存使用比较大的情况下需要考虑这个问题。
所以,这也解释了为什么如果预先给vector声明大小的话使用vector的效率会更高,毕竟如果不声明的话,随着元素的加入,数据一直移到更大的空间上,也不是个好办法。
实现了动态表的是deque,double-ended queue,双端队列,我查了许多中文互联网上的资料,讲真,还是C++中文文档上面说的最简洁明了:
来源:https://zh.cppreference.com/w/cpp/container/deque
std::deque( double-ended queue ,双端队列)是有下标顺序容器,它允许在其首尾两段快速插入及删除。另外,在 deque 任一端插入或删除不会非法化指向其余元素的指针或引用。与 std::vector 相反, deque 的元素不是相接存储的:典型实现用单独分配的固定大小数组的序列,外加额外的登记,这表示下标访问必须进行二次指针解引用,与之相比 vector 的下标访问只进行一次。
deque 的存储按需自动扩展及收缩。扩张 deque 比扩展 std::vector 便宜,因为它不涉及到复制既存元素到新内存位置。另一方面, deque 典型地拥有较大的最小内存开销;只保有一个元素的 deque 必须分配其整个内部数组(例如 64 位 libstdc++ 上为对象大小 8 倍; 64 位 libc++ 上为对象大小 16 倍或 4096 字节的较大者)。
deque 上常见操作的复杂度(效率)如下:
- 随机访问——常数 O(1)
- 在结尾或起始插入或移除元素——常数 O(1)
- 插入或移除元素——线性 O(n)
std::deque满足容器 (Container)、具分配器容器 (AllocatorAwareContainer)、序列容器 (SequenceContainer)和可逆容器 (ReversibleContainer)的要求。
STL还是挺有意思的,但是最近时间有点紧张,就先研究到这里吧
动态表和C++ vector的更多相关文章
- 数据结构(1) 第一天 算法时间复杂度、线性表介绍、动态数组搭建(仿Vector)、单向链表搭建、企业链表思路
01 数据结构基本概念_大O表示法 无论n是多少都执行三个具体步骤 执行了12步 O(12)=>O(1) O(n) log 2 N = log c N / log c N (相当于两个对数进行了 ...
- Tableview中Dynamic Prototypes动态表的使用
Tableview时IOS中应用非常广泛的控件,当需要动态的添加多条不同的数据时,需要用动态表来实现,下面给出一个小例子,适用于不确定Section的数目,并且每个Section中的行数也不同的情况, ...
- [K/3Cloud] 如何从被调用的动态表单界面返回数据
在需要返回数据的地方调用表单返回方法完成数据返回 this.View.ReturnToParentWindow(retData); 在调用界面的回调函数中取出返回结果的ReturnData即可使用. ...
- mybatis操作动态表+动态字段+存储过程
存储过程 statementType="CALLABLE" <!-- 计算金额存储过程--> <update id="getCalcDistributo ...
- Vue+Element的动态表单,动态表格(后端发送配置,前端动态生成)
Vue+Element的动态表单,动态表格(后端发送配置,前端动态生成) 动态表单生成 ElementUI官网引导 Element表单生成 Element动态增减表单,在线代码 关键配置 templa ...
- 简易OA漫谈之工作流设计(六,快捷表单和动态表单)
如果没有表单设计功能,我们一般建物理表,再把表单挂接到流程, 我们可以把外接表单的地址填到表单地址中,地址中会传递一个id. 如果使用外接表单,在审批的时候可能会“不太友好”,因为在审批单上看不到任何 ...
- angularjs 动态表单, 原生事件中调用angular方法
1. 原生事件中调用angular方法, 比如 input的onChange事件想调用angular里面定义的方法 - onChange="angular.element(this).sco ...
- vue 开发系列(八) 动态表单开发
概要 动态表单指的是我们的表单不是通过vue 组件一个个编写的,我们的表单是根据后端生成的vue模板,在前端通过vue构建出来的.主要的思路是,在后端生成vue的模板,前端通过ajax的方式加载后端的 ...
- MYSQL 的静态表和动态表的区别, MYISAM 和 INNODB 的区别
MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良.虽然性能极佳,但却有一个缺点 ...
随机推荐
- ubuntu下apt-get 命令参数
转载:https://blog.csdn.net/linuxzhouying/article/details/7192612 ubuntu下apt-get 命令参数 常用的APT命令参数 apt-ca ...
- Fragment全解析系列
(一):那些年踩过的坑 开始之前 最新版知乎,单Activity多Fragment的架构,响应可以说非常“丝滑”,非要说缺点的话,就是没有转场动画,并且转场会有类似闪屏现象.我猜测可能和Fragmen ...
- 四、SpringBoot出现报错:java.lang.NoSuchMethodError: org.springframework.http.MediaType.equalsTypeAndSubtype(Lorg/springframework/util/MimeType;)Z
idea启动SpringBoot项目后,出现如下错误: 2019-11-19 15:24:44.344 ERROR 39168 --- [nio-8443-exec-1] o.a.c.c.C.[.[. ...
- [转]html中meta作用
meta是html语言head区的一个辅助性标签.几乎所有的网页里,我们可以看到类似下面这段的html代码: <head> <meta http-equiv="cont ...
- Win10删除文件显示删除确认对话框
1.右键单击“回收站”图标:2.在弹出属性窗口中,点击“属性”选项:3.在“回收站”窗口中,在选项“显示删除确认对话框”前面打钩,并单击“确定”按钮:
- git介绍以及常用命令操作
一.git与SVN的对比[面试] ①git是分布式的,SVN是集中式的(最核心) ②git是每个历史版本都存储完整的文件,便于恢复,SVN是存储差异文件,历史版本不可恢复(核心) ③git可离线完成大 ...
- OO ALV 后台运行时错误:Control Framework: Fatal error - GUI cannot be reached
这个错误的原因,是GUI容器依赖GUI的存在,因为它是在后台运行,没有GUI,因此控制错误. 可以通过做一些编码绕过这个. * ALV Grid DATA: R_GRID TYPE REF TO CL ...
- 用泛型方法Java从实体中提取属性值,以及在泛型方法中的使用
public <T> T getFieldValue(Object target, String fieldName, Class<T> typeName) { try { O ...
- fastjson反序列化LocalDateTime失败的问题java.time.format.DateTimeParseException: Text '2019-05-24 13:52:11' could not be parsed at index 10
本地java类 import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; ...
- windows下打开.ipynb文件
windows下打开.ipynb文件1.首先要下载python,设置环境变量2.下载pip,设置环境变量3.打开命令行,进入到python的Scripts文件中,按顺序执行下面三个命令pip inst ...