动态表和C++ vector

最近课上刚刚学了可以根据表中元素的插入和删除动态调整表大小的动态表(dynamic table),就想看一下它有什么实际的应用,第一个想起来的就是C++的vector,直觉中它是最符合动态表特性的了(预先不需要声明大小,当然你要是想声明也没问题,动态插入和删除)。但是下面这篇文章说明了vector不算是一个完全的动态表,因为它的内存占用随着元素的插入和删除是只增加不释放的


来源:https://blog.csdn.net/qq_30835655/article/details/60762196

最近开始更加深入的学习C++,发现了很多以前没注意到但是很重要的知识点。这篇文章主要说vector内存机制和效率问题。

  1. 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前面发生,与网上查询资料有所不同。有待考证,希望阅读者可以在自己的环境下测试,观察结果。

  1. 内存释放

    就像前面所说的,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. 数据结构(1) 第一天 算法时间复杂度、线性表介绍、动态数组搭建(仿Vector)、单向链表搭建、企业链表思路

    01 数据结构基本概念_大O表示法 无论n是多少都执行三个具体步骤 执行了12步 O(12)=>O(1) O(n) log 2 N = log c N / log c N (相当于两个对数进行了 ...

  2. Tableview中Dynamic Prototypes动态表的使用

    Tableview时IOS中应用非常广泛的控件,当需要动态的添加多条不同的数据时,需要用动态表来实现,下面给出一个小例子,适用于不确定Section的数目,并且每个Section中的行数也不同的情况, ...

  3. [K/3Cloud] 如何从被调用的动态表单界面返回数据

    在需要返回数据的地方调用表单返回方法完成数据返回 this.View.ReturnToParentWindow(retData); 在调用界面的回调函数中取出返回结果的ReturnData即可使用. ...

  4. mybatis操作动态表+动态字段+存储过程

    存储过程 statementType="CALLABLE" <!-- 计算金额存储过程--> <update id="getCalcDistributo ...

  5. Vue+Element的动态表单,动态表格(后端发送配置,前端动态生成)

    Vue+Element的动态表单,动态表格(后端发送配置,前端动态生成) 动态表单生成 ElementUI官网引导 Element表单生成 Element动态增减表单,在线代码 关键配置 templa ...

  6. 简易OA漫谈之工作流设计(六,快捷表单和动态表单)

    如果没有表单设计功能,我们一般建物理表,再把表单挂接到流程, 我们可以把外接表单的地址填到表单地址中,地址中会传递一个id. 如果使用外接表单,在审批的时候可能会“不太友好”,因为在审批单上看不到任何 ...

  7. angularjs 动态表单, 原生事件中调用angular方法

    1. 原生事件中调用angular方法, 比如 input的onChange事件想调用angular里面定义的方法 - onChange="angular.element(this).sco ...

  8. vue 开发系列(八) 动态表单开发

    概要 动态表单指的是我们的表单不是通过vue 组件一个个编写的,我们的表单是根据后端生成的vue模板,在前端通过vue构建出来的.主要的思路是,在后端生成vue的模板,前端通过ajax的方式加载后端的 ...

  9. MYSQL 的静态表和动态表的区别, MYISAM 和 INNODB 的区别

    MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良.虽然性能极佳,但却有一个缺点 ...

随机推荐

  1. Mac securecrt 破解

      今天花了好长的时间终于在Mac上把SecureCRT安装成功了,网上教程大多没有截图,破解关键步骤含糊,现在把详细的破解过程和SecureCRT使用教程分享给大家.最后把参考的博客连接附在最下方. ...

  2. python hash 每次调用结果不一样

    import time import multiprocessing device = ['3695a1c7-0fa6-4fa8-a563-8fd462c04af5', '0dfdd431-f9bc- ...

  3. 利用uniGUI中的TUniPageControl实现多页面

    远行效果: 实现代码: procedure TfrmMain.OpenForm(Caption,FormClassName:string);var  i:integer;  sheet:TUniTab ...

  4. Python:目录

    ylbtech-Python:目录 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   作者:ylbtech出处:http://ylbtec ...

  5. 计算机组成原理 — CPU 中央处理器

    目录 文章目录 目录 前文列表 逻辑电路部件 组合逻辑电路 时序逻辑电路 阵列逻辑电路 中央处理器(CPU) 控制单元(控制器) 运算单元(运算器) 存储单元(寄存器组和片内缓存) CPU 的工作原理 ...

  6. 八十三:redis之redis的字符串、过期时间、列表操作

    字符串操作 设置值 set key value 设置有空格的值,加引号 set username 'hello world' 获取值 get key 删除值:del key 清除所有内容:flusha ...

  7. 关于SVN的405错误Server sent unexpected return value (405 Method Not Allowed)的解决办法

    一大早上捣鼓项目提交的时候出现这个错误: svn:server sent unexpected return value 405 method not allowed 百度了很多解决办法都没有解决,看 ...

  8. 重置csr

    重置csr 注意:下面操作仅在刚安装k8s后24小时内有效 分析:kubelet启动后会生成如下文件.kubelet.conf文件决定了csr的存在,如果要想重新获取csr,可以停掉kubelet,删 ...

  9. Winform之跨线程更新UI

    Winform之跨线程更新UI 使用`Invoke`或者`BeginInvoke`与UI线程交互示例 参考及源码 使用Invoke或者BeginInvoke与UI线程交互示例 private void ...

  10. NJCTF (easycrack)

    安装app查看.一个输入框,输入随便输入显示Try again. 放入JEB反编译. 关于输入框监听是第一次见,具体可以看看这个博客https://www.jianshu.com/p/f976c677 ...