动态表和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. skb_buff封装

    可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体进行的,它的重要性和BSD的mbuf类似(看过<TCP/IP详解 卷2>的都知 ...

  2. Java - 单链表

    链表是一种常见的基础数据结构,是一种有序的列表,但不会按照线性顺序存储数据,而是在每一个节点里存储下一个节点的指针(next).链表适合插入.删除,不宜过长,否则会导致遍历性能下降. 以节点方式存储: ...

  3. laravel 文件上传总结

    调用 store 方法会生成唯一的 ID 来作为文件名,如果想获取原件本来的名称可以使用 $file = $request->file('file'); $file->getClientO ...

  4. LC 539. Minimum Time Difference

    Given a list of 24-hour clock time points in "Hour:Minutes" format, find the minimum minut ...

  5. tensorflow神经网络与单层手写字识别

    1.知识点 """ 1.基础知识: 1.神经网络结构:1.输入层 2.隐含层 3.全连接层(类别个数=全连接层神经元个数)+softmax函数 4.输出层 2.逻辑回归: ...

  6. SQL学习(七)试图

    试图是基于SQL语句的结果集的可视化表. 1.创建试图 create view 试图名 as select 语句 如: create view ticketresult as select * fro ...

  7. 根据json生成java实体类

    之前一篇讲过了XML转java实体对象,使用的是JAXB技术,今天给大家推荐一个在线转json到java实体对象: http://www.bejson.com/json2javapojo/new/ 转 ...

  8. 卷积的三种模式:full、same、valid + 卷积输出size的计算

    转自https://blog.csdn.net/u012370185/article/details/95238828 通常用外部api进行卷积的时候,会面临mode选择. 这三种mode的不同点:对 ...

  9. maven的基本使用

    安装: 1.下载maven http://maven.apache.org/ 2.将maven包解压并放置到安装目录 3.添加环境变量M2_HOME,path当中添加;%M2_HOME%\bin; 4 ...

  10. 监控系统-PMM

    Percona Monitoring and Management (PMM)是一款开源的用于管理和监控MySQL和MongoDB性能的开源平台 通过PMM客户端收集到的DB监控数据用第三方软件Gra ...