深刻理解C++STL库常见容器功能和底层
vector底层机制vector本质是一个动态分配连续内存的数组维护了三个指针迭代器_start指向连续内存的起始物理地址_finish指向当前最后一个有效元素下一个位置_end_of_storage指向整块内存的末尾有了三个指针所有方法都是指针运算size()当前元素个数就是finish - startcapacity()当前总容量就是end_of_storage - startempty()是否为空就是判断start finish这里需要注意CSTL设计是左闭右开[start, end)动态扩容机制什么时候会扩容怎么扩容触发场景_finish _end_of_storage时没有空间塞数据了做法一定不是原地扩容OS很难在原内存后面找到一块合适的空间去堆上找一块全新的、更大的内存GCC通常2倍MSVC通常1.5倍把老内存里的数据逐一**拷贝或移动**到新内存调用老内存中所有对象的析构函数释放老内存空间为什么不能原地扩容涉及到缓存命中的问题内存碎片堆上的内存分配是动态、无序的new了一个100字节内存随着程序运行这100字节前后的内存大概率都被其它对象占用了缓存命中CPU从内存中读数据非常慢本质是拷贝CPU内部有L1/L2/L3三级缓存SRAMCPU每次去内存拿数据会把目标地址附近的一整块连续内存通常是64字节一个Cache Line一次性取完如果vector容量满了你想在原地直接把这块内存拉长现实中极大概率是做不到的因为屁股后面紧挨着的那块物理内存已经被别人占了深拷贝、浅拷贝、移动辨析浅拷贝纯粹按位赋值对于内置类型没问题但是对于自定义类型浅拷贝只会拷贝指针的地址这导致了新老vector里的指针指向同一块内存任意一方修改数据会影响到另一方而且会造成Double Free问题深拷贝开辟一块和源vector一样的内存空间并拷贝源vector的数据到新的内存中移动语义老内存若是快要销毁直接把老对象的资源指针偷过来把老对象的指针置空即可注意只有当类明确使用noexcept标记了移动构造函数时vector扩容才会大胆地使用移动语义vector是极其保守的迭代器机制迭代器本质是封装了指针的类对象并重载了、--、\*、-等运算符对于使用者来说无论底层是连续数组还是复杂的红黑树都可以统一使用it来走向下一个元素用*it来获取数据。它完美屏蔽了底层数据结构内存布局的差异这就是C封装的思想屏蔽底层实现细节暴露给用户一套标准的操作接口用户不用关心底层到底是怎么实现的但是迭代器很容易失效我们先讲连续内存派vector、string为例失效场景1触发动态扩容原因vector扩容是去堆上找一块新内存然后把老内存释放掉。如果在扩容前保存了一个迭代器扩容后这个迭代器里面包着的指针还指着那块已经被系统回收的老内存解决 扩容后绝对不要使用任何之前保存的迭代器、指针或引用。如果需要记录位置记录索引而不是迭代器失效场景2中间插入或删除原因 当调用v.erase(it)删除中间某个元素时为了保持物理连续性vector会把该位置之后的所有元素统统往前挪一步。此时手里的it虽然物理地址没变但它指向的数据已经变成了原本在它后面的那个元素如果你再执行一次it你就会漏掉一个元素解决利用erase的返回值。erase会自动返回一个指向被删除元素下一个位置的有效迭代器// 错误写法会漏删元素或者越界崩溃while(it!v.end()){if(*it2)v.earse(it);it;}// 正确写法while(it!v.end()){if(*it2)itv.earse(it);// 删除了erase已经帮你走了一步接管新迭代器就好elseit;// 只有在没有删除的情况下才会手动往后走}高频APIpush_back()和emplace_back()push_back的本质接收一个已经构造好的对象或者右值。在底层会调用拷贝构造函数或移动构造函数将数据放进vector的内存中emplace_back的本质它的底层使用了 C11 的可变参数模板和完美转发。它不接收对象而直接接收“构造对象所需的参数”然后在vector_finish指针指向的那块裸内存上直接调用定位 newPlacement new原位构造对象emplace_back伪代码templatetypename...Args// 1. 可变参数模板接收任意数量参数voidemplace_back(Args...args){// 万能引用接住所有左值/右值// 检查容量不够就扩容...if(_finish_end_of_storage){// ... 扩容逻辑}// 核心动作// 2. 完美转发std::forwardArgs(args)... 原封不动地把参数状态传下去// 3. 定位 new在 _finish 裸指针指向的内存上直接原地构造对象new((void*)_finish)T(std::forwardArgs(args)...);// 指针后移_finish;}为什么emplace_back比push_back快两者执行v.push_back(User(张三, 18))和v.emplace_back(张三, 18)时的动作push_back路线在外面的栈上构造一个临时的User对象把临时对象作为参数传进push_backpush_back内部调用移动构造函数或拷贝构造把数据挪到_finish的内存上析构外面栈上的那个临时User对象emplace_back路线把字符串张三和整数18打包完美转发到底层在_finish内存上直接调用带参构造函数一次性成型零次临时对象创建零次拷贝/移动构造零次临时对象析构总结只要是往容器尾部追加元素无脑优先使用emplace_backclear()clear的本质是逻辑清空循环调用所有有效元素的析构函数然后把_finish指针拉回到_start的位置size归零但是capacity还占着呢内存并没有释放把这个释放时机交给用户处理那怎么释放这块大内存呢std::vectorintv;// ... 假设 v 经历了大量插入capacity 暴涨到了 100000但现在 size 只有 10// v.clear(); // 没用capacity 还是 100000// 利用匿名临时对象交换指针std::vectorint(v).swap(v);std::vectorint(v)触发拷贝构造创建了一个匿名临时对象只申请刚好能装下 10 个元素的内存它的capacity就是 10.swap(v)将临时对象内部的三个指针与原来v内部的三个指针进行互换。互换后v的容量成功收缩到了 10。而那个匿名临时对象手里现在攥着原来那 100000 的庞大内存这行代码一结束匿名临时对象生命周期到期自动调用析构函数顺理成章地带着那块庞大的内存同归于尽resize()和reserve()reserve(n)预留空间操作的是_end_of_storage指针动作和OS交涉“给我准备能装n个元素的连续空间”结果只改变capacity绝对不改变size_finish指针保持不动新开辟的空间未初始化绝对不能v[index]去访问resize(n)调整大小操作_finish指针动作它不仅要求物理空间到位还会在这些空间上真正去构造对象结果它同时改变capacity和size。如果n大于当前的size它会调用对象的默认构造函数把多出来的坑填满。填满后可以直接用v[index]去访问修改未完待续~

相关新闻

把 SAP 系统真正跑在 IPv6 上:从实例开关到 AS Java、DNS 与双栈治理的完整实践

把 SAP 系统真正跑在 IPv6 上:从实例开关到 AS Java、DNS 与双栈治理的完整实践

在很多 SAP Basis 团队看来,IPv6 像是网络团队的话题,和应用层关系不大。可一旦系统里同时存在 SAP Gateway、ICM、RFC、Web 通信、Fiori 入口、跨机房互联,甚至还要和云端服务打通,这件事就不再只是地址规划,而会直接影响实例监听方式、主机名解析顺序、内部系统通信路径…

2026/5/17 12:40:07 阅读更多 →
把 SAP 表索引讲透:从 DDIC 主索引、二级索引到 Full Text 与 Fuzzy Search 的设计与取舍

把 SAP 表索引讲透:从 DDIC 主索引、二级索引到 Full Text 与 Fuzzy Search 的设计与取舍

在 ABAP Dictionary 里,数据库索引并不是可有可无的附属品,而是决定查询路径、响应时间,甚至决定某类搜索能力能否成立的核心结构。对一个 DDIC 数据库表来说,系统一定会基于主键定义一个 Primary Index。除此之外,还可以继续定义 Secondary Index,在特定场景下还会涉及 …

2026/5/17 12:40:07 阅读更多 →
霍夫曼编码:数据压缩的魔法树

霍夫曼编码:数据压缩的魔法树

1951年,麻省理工学院。 一个叫大卫霍夫曼的研究生正面临一个选择:要么参加期末考试,要么写一篇学期论文。论文的题目是导师罗伯特法诺给的—— “找到最优的二进制编码方法。” 法诺自己研究这个问题多年,没有找到最优解。他把这个…

2026/7/3 4:16:34 阅读更多 →

最新新闻

PIC18F8722外部EEPROM存储扩展实战指南

PIC18F8722外部EEPROM存储扩展实战指南

1. 为什么需要外部EEPROM存储扩展在嵌入式系统开发中,PIC18F8722这类微控制器自带有限的内部存储空间。以PIC18F8722为例,其内部EEPROM容量仅为1024字节(1KB),这对于需要存储大量配置参数、历史数据或日志记录的应用场…

2026/7/3 17:21:52 阅读更多 →
高效低查重!AI教材生成工具助力教师轻松完成教材编写

高效低查重!AI教材生成工具助力教师轻松完成教材编写

谁没有在编写教材时感到困惑呢? 面对一页空白的文档,沉思了半个多小时,知识点的整理似乎毫无头绪——是先讲解基本概念,还是先分享案例呢?章节的划分该按照逻辑、还是依据课时呢?不断修改的大纲总是无法符…

2026/7/3 17:21:52 阅读更多 →
从8万美元跌至千元级,车载激光雷达成本暴跌96%背后:芯片化、规模化与全场景落地实战

从8万美元跌至千元级,车载激光雷达成本暴跌96%背后:芯片化、规模化与全场景落地实战

目录 摘要 一、行业综述:激光雷达从天价科研设备到民用标配的蜕变 1.1 十年价格迭代核心数据 1.2 市场格局与产业现状 二、核心降本逻辑一:芯片化架构重构,从分立器件到单芯片集成 2.1 传统分立架构的致命成本缺陷 2.2 芯片化自研的核心降本原理 2.3 头部厂商差异化…

2026/7/3 17:19:52 阅读更多 →
结构化数据 + GEO:让 AI 真正“读懂”你的网站

结构化数据 + GEO:让 AI 真正“读懂”你的网站

如果你的网站内容连 AI 都“看”不明白,再好的产品和服务也会在生成式搜索时代石沉大海。而让 AI 精准理解你的第一步,就藏在看似不起眼的 Schema 标记里。 一、当搜索引擎变成“答案引擎” 过去十年,SEO 的核心是取悦搜索引擎的爬虫——让它…

2026/7/3 17:17:52 阅读更多 →
如何在Steam Deck上实现多平台游戏启动器的一键整合

如何在Steam Deck上实现多平台游戏启动器的一键整合

如何在Steam Deck上实现多平台游戏启动器的一键整合 【免费下载链接】NonSteamLaunchers-On-Steam-Deck Installs the latest UMU/GE-Proton and Non Steam Launchers under 1 Proton prefix folder and adds them to your steam library. Installs... Battle.net, Epic Games,…

2026/7/3 17:17:52 阅读更多 →
城配内卷时代:谁的“管理颗粒度”更细,谁就能活下来

城配内卷时代:谁的“管理颗粒度”更细,谁就能活下来

城配行业正在经历一场残酷的洗牌。市场规模早已突破万亿,但行业集中度极低——这意味着成千上万家中小车队在同一条赛道里拼价格、拼人效。订单还在涨,单价却在下滑。过去靠“多拉快跑”就能赚钱的日子一去不返,如今拼的是谁的成本更低、谁的…

2026/7/3 17:15:51 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻