让C++变得更加容易:vector的增长机理

vector的扩容机制—为何是1.5倍或者是2倍【通俗易懂】 在 C++ 编程中,Vector 是一种常用的动态数组容器。其大小是可以动态调整的,而在扩容操作中,Vector 通常会将容量增加为原来的两倍。本篇博客将详细介绍 Vector 扩容的原理、扩容过程,并解释为何选择两倍进行扩容。如果新空间大小为旧空间大小+1,也就是边插入边扩容,这样每一次插入都要进行拷贝,时间复杂度为O(n),效率非常低下。如果新空间大小为旧空间大小+k,其中k是一个固定的增量,那么在每次扩容时,新空间的大小会增加k个单位。 阅读详情

http://www.royaloo.com/articles/articles_2003/cme02.htm


“默认情况下,C++标准库提供了合理的性能”。如果你对“合理的”一词暗含的意思有过好奇,请接着读下去…… 

引言 

假设我们希望从一个文件中将一串类型为double的值读进一个数据结构中,从而允许我们高效地访问这些值,通常的方法如下: 

vector<double> values;
double x;
while (cin >> x)
    values.push_back(x); 

当循环结束时,values会容纳有所有的值,我们将可以通过values[i]高效地访问任何值。 

在直觉上,标准库vector类就像一个内建数组:我们可以认为它在单块连续的内存中容纳其元素。实际上,尽管C++标准没有明确要求vector的元素要占用连续的内存,然而标准委员会在2000年10月份的会议上裁定此项要求的遗漏归因于工作上的疏忽,并且投票表决将其作为技术勘误的一部分而包含进来。这个迟到的要求谈不上是多大的痛苦,因为每一个现有的vector实现本来就是以这种方式工作的。 

如果一个vector的元素位于连续的内存中,我们就很容易明白它是如何高效地访问个体元素的 — 只要使用与内建数组相同的机制就可以了。不过,要弄明白一个vector实 现是如何处理高效增长的问题就不是这么简单了,因为这种增长将不可避免地涉及到将元素从一块内存区域拷贝到另外一块内存区域。尽管现代处理器通常特别擅长 于将一块连续的数据从内存的一个地方拷贝到另一个地方,然而这样的拷贝并非是免费的午餐。因此,思考一个标准库实现可能是如何处理vector的增长而又不消耗过量的时间或空间,很有意义。 

本文的余下部分将讨论一个用于管理vector增长的简单而高效的策略。 

大小和容量 

要想搞清楚vector类的工作机制,首先要清楚它并不仅仅是一块内存。相反,每一个vector都关联有两个“尺寸”:一个称为 大小(size),表示vector容纳的元素的数量;另一个称为容量(capacity),表示可被用来存储元素的内存总量。比方说,假如v是一个vector,那么v.size()和v.capacity()则分别返回v的 大小和容量。你可以想象一个vector看起来如下: 

 

当然了,在vector尾部留有额外的内存的用意在于,当使用push_back向vector追加元素时无需分配更多的内存。如果邻接于vector尾部的内存当时恰好未被占用,那么vector的增长只要将那块内存合并过来即可。然而这样的好运气极其罕见,大多数情况下需要分配新的内存,然后将vector现有的元素拷贝到那块内存中,然后销毁原来的元素,最后归还元素先前占用的内存。在vector中留有额外的内存的好处在于,这样的重新分配(代价可能很昂贵)不会每当试图向vector追加一个元素时都发生。 

重新分配内存的代价有多高昂?它涉及如下四个步骤: 

  • 为需要的新容量分配足够的内存;

  • 将元素从原来的内存拷贝到新内存中;

  • 销毁原来的内存中的元素;

  • 归还原来的内存。 

如果元素的数目为n,那么我们知道步骤(2)和(3)都要占用O(n)的时间,除非分配或归还内存的代价的增长超过O(n),否则这两步将在全部运行时间中占居支配地位。因此我们可以得出结论:无论用于重新分配的容量(capacity)是多少,重新分配一个 大小(size)为n的vector需要占用O(n)的时间。 

这 个结论暗示了一种折衷权衡。假如在重新分配时请求大量的额外内存,那么在相当长的时间内将无需再次进行重新分配,因此总体重新分配操作消耗的时间相对较 少,这种策略的代价在于将会浪费大量的空间。另一方面,我们可以只请求一点点额外的内存,这么做将会节约空间,但后继的重新分配操作将会耗费时间。换句话 说,我们面临一个经典的抉择:拿时间换空间,或者相反。

重新分配策略 

作为一个极端的例子,假定每当填充vector一次我们就将其容量增加1个单位,这种策略耗费尽可能少的内存空间,但每当追加一个元素时都要重新分配整个vector。我们说过,重新分配一个具有n个元素的vector占用O(n)的时间,因此,如果我们从一个空vector开始并将其增长到k个元素,那么占用的总时间将会是O(1+2+...+k)或者O(k2),这太可怕了!有没有更好的办法呢? 

比方说,假如不是以步幅1来增长vector的容量,而是以一个常量C的步幅来增长它将会如何?很明显这个策略将会减少重新分配的次数(基于因子C),所以这当然是一种改进,但这个改进到底有多大呢? 

理解这个改进的方式之一是要认识到此一新策略将针对每C个元素块进行一次重新分配。假设我们为总量为KxC个元素分配K块内存,那么,第一次重新分配将会拷贝C个元素,第二次将会拷贝2xC个元素,等等。Big-O表示法不考虑常量因子,因此我们可以将所有的C因子分摊开来而获得O(1+2+...+K)或者O(K2)的总时间。换句话说,时间仍然是元素个数的二次方程,不过是带有一个小得多的因子罢了。 

撇 开较小的因子不谈,“二次行为”仍然太糟糕,即使有一个快速的处理器也是如此。实际上,对于快速的处理器来说尤其糟糕,因为快速的处理器通常伴有大量的内 存,而访问具有大量内存的快速处理器的程序员常常试图用尽那些内存(这是迟早的事)。这些程序员往往会发现,如果在运行一个二次算法的话,处理器的速度于 事无补。 

我们刚刚证实,一个希望能以小于“二次时间”而分配大型vector的实现是不能使用“每次填充时以常量步幅增长vector容量”的策略的,相反,被分配的附加内存的数量必须随着vector的增长而增长。这个事实暗示存在一种简单的策略:vector从单个元素开始而后每当重新分配时倍增其容量,如何?事实证明这种策略允许我们以O(n)的时间构建一个有着n个元素的vector。 

为了理解是如何获得这样的效率的,考虑当我们已经完全填满它并打算对其重新分配时的vector的状态: 

 

自最近一次重新分配内存以来被追加到vector中的元素有一半从未被拷贝过,而对于那些被拷贝的元素而言,其中一半只被拷贝了一次,其余的一半被拷贝了两次,以此类推。 

换句话说,有n/2的元素被拷贝了一次或多次,有n/4的元素被拷贝了两次或多次,等等。因此,拷贝元素的总数目为n/2 + n/4 +...,结果可以近似为n(随着n的增大,这个近似值越发精确)。撇开拷贝动作不谈,有n个元素被追加到了vector中,但操作占用的时间总量仍然是O(n)而不是O(n2)。 

讨论 

C++标准并没有规定vector类必须以某种特定的方式管理其内存,它只是要求通过重复调用push_back而创建一个具有n个元素的vector耗费的时间不得超过O(n),我们刚才讨论的策略可能是满足此项要求的最直截了当的一种。 

因为对于这样的操作来说vector具有优秀的时间性能,所以没有什么理由避免使用如下循环: 

vector<double> values;
double x;
while (cin >> x)
    values.push_back(x); 

是的,当其增长时,实现将会重新分配vector的元素,但是,如果我们事先能够预测vector最终 大小的话,这个重新分配耗费的时间将不会超过“一个常量因子”可能会占用的时间。 

练习 

1.设想我们通过以如下方式编写代码而努力使我们那个小型循环速度更快: 

while (cin >> x)
{
    if (values.size() == values.capacity())
        values.reserve(values.size() + 1000);
    values.push_back(x);
}

效果将会如何?成员函数reserve进行一次重新分配,从而改变vector的capacity,使其大于或等于其参数。 

2.设想不是每次倍增vector的大小,而是增大三倍,在性能上将会产生什么样的影响?特别是,创建一个具有n个元素的vector的运行时间仍然为O(n)吗? 

3.设想你知道你的vector最终将拥有多少元素,在这种情况下,在填充元素之前你可以调用reserve来预先分配数量合适的内存。试一试你手 头的vector实现,看看调用reserve与否对你的程序的运行时间有多大的影响。

C++栈溢出问题解析:从静态数组到现代容器的内存管理实践 在C++程序开发中,内存管理是核心基础概念之一,涉及栈、堆和静态存储区等不同内存区域的原理与使用。栈空间作为函数调用和局部变量的存储区域,其大小有限,通常只有1-8MB,当在函数内部声明大型静态数组(即固定大小的局部数组)时,容易因超出栈容量而导致栈溢出,引发程序崩溃。这一问题的技术价值在于理解不同内存区域的特性和生命周期管理,对于编写健壮、高效的C++代码至关重要。在实际应用场景中,如递归算法、图形处理或大规模数据计算时,不当的栈内存使用会成为性能瓶颈和稳定性隐患。本文通过分析栈溢出问题的根源,对比静态数 阅读详情

相关推荐

C++程序内存不足崩溃:深层机理、诊断与根治策略

内存管理是程序稳定运行的基石,其核心在于操作系统如何为进程分配和调度物理内存与虚拟内存。当程序申请的内存超过系统可用上限时,便会触发访问违规、堆损坏或栈溢出等异常,导致进程被强制终止或陷入不可恢复状态,表现为程序闪退或崩溃。理解内存不足的深层机理,对于构建高可用的C++应用至关重要。在工程实践中,开发者需掌握系统性诊断工具,如核心转储分析、内存监控(Valgrind、AddressSanitizer)和代码静态审查,以定位内存泄漏、碎片化及分配失败等根源问题。针对高频出现的**内存不足**和**程序崩溃**

weixin_30808693的博客 383

1.算法进阶——Vector底层原理与倍增扩容方法 详解

开新专栏啦,该系列是 极客时间-黄清昊-算法实战高手课 的学习笔记,以此为媒介深挖我们之前提到但没展开的内容。希望大家一起学习,一起进步。 回顾 我们在之前的内容,分享过vector作为顺序容器的一部分内容: c++stl 我把之前的内容挪一部分来: 动态数组,用于 O(1) 的随机读取. 实现原理: 由三个指针实现, 第一个指针指向vector起始位置 第二个指针指向目前有数据的末尾 第三个指针指向整个vector容器所占内存的末尾。 由此实现: template<class _Ty, .

strive for the best 的博客 2823

C++ Primer核心精要:从string、vector到函数与类的实战解析

在C++编程中,理解核心数据结构与内存管理机制是构建高效、安全程序的基础。string和vector作为标准库中最常用的容器,其设计体现了C++资源管理(RAII)的核心思想:string通过自动内存管理解决了C风格字符串的安全隐患,而vector通过容量与大小的分离机制实现了高效的动态数组。掌握参数传递的三种方式(传值、传引用、传指针)及其适用场景,是编写高性能函数的关键。这些概念共同支撑起现代C++的工程实践,广泛应用于系统开发、游戏引擎、高频交易等对性能和可靠性要求极高的领域。本文以《C++ Prim

weixin_30325971的博客 383

Vector原理源码分析

Vector是继承了AbstractList抽象类并且实现了List接口, 其底层数据结构为Object[]数组。 总结一句话Vector与ArrayList类似,只是其在修改数据的方法上加了synchronized方法。所以Vector是一种线程安全的ArrayList

清露草木的博客 432

高效率STLvector可替换易的数组多数组大数据操作 支持库可静编-易语言

注意 : 本来就没有 学习 价值(有也在于英文版),只有 实用 价值,写给易调用而已。 详细可以看http://blog.youkuaiyun.com/phoebin/article/details/3864590  本来就是STL类和函数只是易调用不了,写成支持库可调用。 带常用基本排序算法: 大数据 , 数组反转 , 数组排序,去重复,速度快效率高。分割文本||整数 也有代替函数 。 可以静态编译。 详细请看例程。 注意两个版本不同。中文版就是删除了一些不要的功能和功能合拼,这样符合易。里面功能应该都够用的了。

易语言分割数字文本到整数数组模块

易语言模块:直接把数字文本分割到整数数组 例如: 待分割文本为: a="1|2|3|4|5" 结果: a={1,2,3,4,5} 省去了把分割好的文本再转换为整数的这一步

C++之vector增长策略

C++之vector增长策略

weixin_65743593的博客 320

单细胞实战之数据净化三部曲:细胞周期校正、双胞体剔除与RNA污染清理——技术解析与实战指南

本文深入解析单细胞数据分析中至关重要的数据净化步骤,即细胞周期校正、双胞体剔除与RNA污染清理。通过实战代码演示与原理剖析,指导读者如何有效识别并去除技术噪音,从而获得更纯净、可靠的数据,为后续的细胞分群、差异表达等高级分析奠定坚实基础。

weixin_29274337的博客 204

vector的增长机理

让C++变得更加容易:vector的增长机理 2004-05-15    Andrew Koenig Barbara E. Moo 荣耀(翻译)    www.royaloo.com

JonnyLai 2337

蓝牙开发实战:如何用Wireshark抓取HCI协议包(附完整分析步骤)

本文详细介绍了如何使用Wireshark抓取和分析蓝牙HCI协议包,包括环境搭建、过滤器配置技巧、数据包深度解析及高级调试方法。通过实战案例,帮助开发者快速定位蓝牙连接问题,提升调试效率。

cuda7parallel的博客 1148

2023-10-20 - MacBook - 优化外接2k显示器体验:告别字小与字虚的终极指南

本文针对MacBook外接2K显示器时普遍遇到的字体过小和边缘发虚问题,提供了终极解决方案。通过深入解析macOS的HiDPI与Retina显示原理,并详细指导使用SwitchResX等工具开启隐藏的HiDPI模式,实现完美的2倍整数缩放,从而在保证字体大小合适的同时获得清晰锐利的显示效果,彻底告别字小与字虚的困扰。

solidity8miner的博客 476

深入理解vector list deque——存储结构机理

先来看一下 c++标准模板库中,容器类vector、list 、deque都属于顺行性容器(所谓顺序性容器是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集,每个元素都有固定的位置,这个位置与元素无关,只与插入删除的先后顺序有关);都可以用来存储一组类型相同的对象;不同于内置数据类型数组的是,vector、list、deque的大小不是固定的,他们支持动态增长。         之所以

bruceyang2009lzu的专栏 7345

ABAP on HANA/CDS view/AMDP 大乱炖

文章目录1. HANA的三大杀器1.1 内存式数据库1.2 多核架构1.3 列式存储1.3.1 数据压缩1.3.2 并行处理1.3.3 聚集和索引1.3.4 代码下移2. CDS 概念2.1 ABAP 开发环境2.2 OPEN SQL语句2.3 CDS 到底是啥2.4 CDS 类型2.5 创建CDS view3. CDS 实战3.1 Annotation注解3.2 Case 语句3.3 数学计算3.4 字符串表达3.5 货币转换3.6 聚集语句3.7 Join3.8 Union3.9 参数3.10 作为子C

weixin_45689053的博客 4782

【STL】vector容器如何使用?

在使用上,完整的成员类型:std:vecter<存放的元素的类型>:上面的成员类型 在使用上,完整的vector类型: std:vecter<存放的元素的类型>创建1.vectorc数据类型>v;2.vectorc数据类型> v(size_type count);3.vector<数据类型> v(size_type count,数据的类型value);4.vector<数据类型>v(任意类型的输入迭代器 first,任意类型的输入送代器 last)

phantomthief1412的博客 2405

TOPCon vs PERC:2024年光伏电站投资,哪种技术更划算?

本文深入分析了2024年光伏电站投资中TOPCon与PERC技术的财务表现。通过对比初始系统成本、全生命周期发电量及环境适应性,指出当前TOPCon组件与PERC价差已显著缩小,其更高的转换效率和更低的衰减率能有效降低度电成本并提升项目内部收益率,为投资者提供更具长期价值的方案。

p5l2m9n4o6q的博客 266

HFSS学习记录——0. HFSS软件安装(ANSYS Electromagnetics Suite 2023 R1)

HFSS 2023 R1安装步骤

plmxu34的博客 15万+

【C++】vector的使用与模拟实现

构造函数声明接口说明vector()(重点)无参构造构造并初始化 n 个 valvector(const vector& x)(重点)拷贝构造使用迭代器进行初始化构造。

m0_74824865的博客 285

python实现MACD策略背离点的判断 (附完整源码)

python实现MACD策略背离点的判断 (附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 665

程序员成长之旅——vector容器

程序员成长之旅——vector容器vector的常见使用vector的定义vector iterator的使用vector增删查改vector常见问题vector的底层实现原理以及实现机制vector空间增长问题vector< char >和string的区别vector迭代器失效问题 vector的常见使用 vector的定义 构造函数声明 vector() //无参构造 vecto...

wuweiwuju___的博客 422

【ROS2】手眼标定:moveit2_handeye_calib使用详解

moveit2_handeye_calib MoveIt2 官方的 手眼标定功能包,包含标定插件、图形化界面和示例程序。提供基于 ArUco/ChArUco 标定板的采集与求解功能。标定板下载aruco在线生成器。

郭老二 246

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C++ STL vector底层实现:从内存管理到移动语义的深度解析

动态数组是计算机科学中基础且核心的数据结构,它通过连续内存块存储元素,支持随机访问。其底层原理涉及内存分配策略、对象生命周期管理和迭代器设计。在C++中,标准模板库(STL)的vector容器正是动态数组的经典实现,它通过智能的内存增长策略(如2倍扩容)和精细的资源管理,在性能与易用性间取得平衡。现代C++引入的移动语义和noexcept优化进一步提升了容器在资源转移时的效率,而RAII机制和异常安全保证则确保了程序的健壮性。这些技术广泛应用于需要高效数据存储和访问的场景,如游戏开发、高频交易系统和科学计算

weixin_34335458的博客 383

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

上一篇: Moblin术语表
下一篇: GTK里的label控件 颜色 大小 等
bitscro
博客等级 码龄21年 19粉丝 41原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值