动态表和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:有索引的顺序访问方法)所改良.虽然性能极佳,但却有一个缺点 ...
随机推荐
- 关于OpenModelica的编译
由于工作需要,最近对OpenModelica进行二次开发,由于国内资料也比较少,所以踩了一些坑,近期计划把OpenModelica的编译,msys,及OpenModelica里面比较关键的部分OMEd ...
- 在ubuntu16.04-32bits 下编译vlc和vlc-qt开源项目
软件版本: Ubuntu14.04 32位 Qt5.4.0 32位 开源项目: vlc2.2.4: wget http://download.videolan.org/pub/v ...
- TensorFlow 学习(1)——第一个程序:线性回归
目前这个程序还有很多地方没有搞懂,先跑一跑例程看看效果如何.从结果来看,最终的训练成果能够接近于预设的数据
- LC 856. Score of Parentheses
Given a balanced parentheses string S, compute the score of the string based on the following rule: ...
- Java 中nextLine()方法没有执行直接跳过解决办法
使用Java的Scanner类nextLne()方法从显示器输入数据时,nextInt()后面的nextLine()直接跳过没有执行: 截图:第三个输入直接跳过 通过上网的查找我终于发现了问题出在哪里 ...
- asp.netMVC中配置automap
第一.新建类库,以解决方案名XXX为例,建立子类库名为 XXX.AutoMapper. 第二. XXX.AutoMapper类库中,添加对automap的引用. 第三.创建映射文件类 ModelPr ...
- 嵌入式【杂记--手机芯片与pc】
手机.身边的移动设备大多数是嵌入式计算机,pc也是计算机,只是功耗上很大. 手机所采用的大多数芯片是英国ARM公司的架构coretom A系列 core, Intel公司采用自己的架构设计的芯片适用于 ...
- thinkphp6.0 nginx 配置
location / { index index.php; #如果文件不存在则尝试TP解析 if (!-e $request_filename) { rewrit ...
- 类属性与对象实现,init方法的作用,绑定方法,绑定方法与普通函数的区别,继承,抽象与继承,派生与覆盖
今日内容: 1.类属性与对象属性 2.init方法的作用 3.绑定方法 4.绑定方法与普通函数的区别(非绑定方法) 5.继承 6.抽象与继承 7.派生与覆盖 1.类属性与对象属性 类中应该进存储所有对 ...
- 04-再探JavaScript
一. DOM介绍 1. 什么是DOM? DOM:文档对象模型.DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构. 目的其实就是为了能让js操作html元素而制定的一个规范. DOM就 ...