C++ STL CookBook 6:STL Containers (I)
目录
顺序容器
关联容器
容器适配器
使用统一擦除函数从容器中删除指定项
在恒定时间内对一个对排序不敏感的vector中删除项目
如果不确定自己访问容器会不会越界,那就使用.at方法而不是[]
在我们开始之前,先来回顾一下传统的经典的几个容器!
顺序容器
顺序容器提供了一个接口,其中元素按顺序排列。虽然您可以按顺序使用元素,但其中一些容器使用连续存储,而其他容器则不使用。STL 包括这些顺序容器:
-
数组是一个固定大小的序列,在连续存储中保存特定数量的元素。一旦分配,它就不能改变大小。这是最简单、最快的连续存储容器。
-
向量就像一个可以缩小和增大的数组。它的元素是连续存储的,因此更改大小可能涉及分配内存和移动数据的费用。向量可能会保留额外的空间以减轻这种成本。从向量后面以外的任何位置插入和删除元素将触发元素的重新对齐以保持连续存储。列表是一种双向链接列表结构,允许在常数(O(1))时间内插入和删除元素。遍历列表的时间复杂度为线性 O(n)。
-
单链接变体可用作 forward_list,它仅向前迭代。forward_list 使用的空间较少,并且比双向链接列表更高效,但缺少一些功能。
-
双端队列(通常发音为 deck)是一种双端队列。它是一个可以在两端扩展或收缩的顺序容器。双端队列允许随机访问其元素,就像向量一样,但不保证连续存储。
关联容器
关联容器将一个键与每个元素关联。元素由其键引用,而不是其在容器中的位置。 STL 关联容器包括以下容器:
-
集合是一种关联容器,其中每个元素也是其自己的键。元素通常是按某种二叉树排序的。集合中的元素是不可变的,无法修改,但可以插入和删除。集合中的元素是唯一的,不允许重复。集合根据其排序运算符按顺序迭代。
-
多集类似于具有非唯一键的集合,其中允许重复。
-
unordered_set 类似于不按顺序迭代的集合。元素不按任何特定顺序排序,而是根据其哈希值进行组织以便快速访问。
-
unordered_multiset 类似于具有非唯一键的 unordered_set,其中允许重复。
-
映射是键值对的关联容器,其中每个键都映射到特定值(或有效负载)。键和值的类型可能不同。键是唯一的,但值不是。映射根据其排序运算符按其键的顺序进行迭代。 多映射类似于具有非唯一键的映射,其中允许重复的键。
-
unordered_map 类似于不按顺序迭代的映射。
-
unordered_multimap 类似于具有非唯一键的 unordered_map,其中允许重复。
容器适配器
容器适配器是封装底层容器的类。容器类提供一组特定的成员函数来访问底层容器元素。STL 提供以下容器适配器:
-
堆栈提供 LIFO(后进先出)接口,其中元素只能从容器的一端添加和提取。底层容器可以是向量、双端队列或列表之一。如果未指定底层容器,则默认为双端队列。
-
队列提供 FIFO(先进先出)接口,其中元素可以添加到容器的一端并从另一端提取。底层容器可以是双端队列或列表之一。如果未指定底层容器,则默认为双端队列。
-
priority_queue 根据严格的弱排序将最大值元素保持在顶部。它提供对最大值元素的恒定时间查找,但插入和提取的时间对数为代价。底层容器可以是向量或双端队列之一。如果未指定底层容器,则默认为向量。
使用统一擦除函数从容器中删除指定项
在 C++20 之前,通常使用擦除-删除习语来有效地从 STL 容器中删除元素。这有点麻烦,但负担不大。通常使用这样的函数来完成任务:
template<typename Tc, typename Tv>
void remove_value(Tc & c, const Tv v) {auto remove_it = std::remove(c.begin(), c.end(), v);c.erase(remove_it, c.end());
}
嘿!实际上是这样的:
std::remove() 函数来自
<algorithms>标头。std::remove()搜索指定的值并通过从容器末尾向前移动元素来删除它。它不会改变容器的大小。它返回一个超出移位范围末尾的迭代器。然后,我们调用容器的 erase() 函数来删除剩余元素。
现在,使用新的统一擦除函数,这个两步过程简化为一步:
std::erase(c, 5); // 与 remove_value() 函数相同
这个函数调用与我们上面编写的 remove_value() 函数执行相同的操作。还有一个使用谓词函数的版本。例如,要从数字容器中删除所有偶数值:
std::erase_if(c, [](auto x) { return x % 2 == 0; });

当我们调用 remove(c.begin(), c.end(), 5) 时,算法会从 begin() 迭代器开始搜索匹配元素。对于找到的每个匹配元素,它会将下一个元素移到其位置。它会继续搜索和移动,直到到达 end() 迭代器。
结果是一个容器,其中所有剩余元素都在开头,没有被删除的元素,并且按其原始顺序排列。end() 迭代器保持不变,剩余元素未定义。我们可以像这样可视化操作:
如你所见,这就是remove_iterator的基本原理,那就是不停的拷贝元素覆盖。
在恒定时间内对一个对排序不敏感的vector中删除项目
使用统一擦除函数(或擦除-删除习语)从向量中间删除项目需要 O(n)(线性)时间。这是因为必须从向量末尾移动元素以填补已删除项目的间隙。
如果向量中项目的顺序不重要,我们可以优化此过程以花费 O(1)(常数)时间。方法如下。
template<typename T>
void quick_delete(T& v, size_t idx) {if (idx < v.size()) {v[idx] = move(v.back());v.pop_back();}
}
如你所见,其实很简单,就是简单的交换一下最后一个元素和目标元素然后直接使得长度减一,但是注意!必须是对那些排序不敏感的!
如果不确定自己访问容器会不会越界,那就使用.at方法而不是[]
是的,我们一直喜欢这样写来访问容器的元素:
vector v{ 19, 71, 47, 192, 4004 };
auto & i = v[2];
当然,一些人会争论我们应该这样写更好:
auto & i = v.at(2);
两者的区别何在呢?答案是:
at() 函数会进行边界检查,而 [] 运算符则不会。
这是有意为之,因为它允许 [] 运算符与原始 C 数组保持兼容性。让我们更详细地研究一下。
这里,我们来直接使用 [] 运算符直接访问向量中的第三个元素。与 C++ 中的大多数顺序对象一样,索引从 0 开始,因此第三个元素是数字 2。
向量有五个元素,编号为 0 到 4。如果我尝试访问元素编号 5,则会超出向量的边界:
vector v{ 19, 71, 47, 192, 4004 };
auto & i = v[5];
cout << format("element is {}\n", i);
element is 0
这个结果非常具有欺骗性。这是一个常见的错误,因为人类倾向于从 1 开始计数,而不是从 0 开始。但不能保证向量末尾之后的元素具有任何特定值。更糟糕的是,[] 运算符会默默地允许您写入超出向量末尾的位置:
vector v{ 19, 71, 47, 192, 4004 };
v[5] = 2001;
auto & i = v[5];
cout << format("element is {}\n", i);
element is 2001
现在已经写入不受我们控制的内存,对于MSVC的库,他会给我们惊喜:

越界内存访问是安全漏洞的主要原因之一。
解决方案是尽可能使用 at() 成员函数,而不是 [] 运算符:
vector v{ 19, 71, 47, 192, 4004 };
auto & i = v.at(5);
cout << format("element is {}\n", i);
现在我们得到一个运行时异常:
代码编译时没有错误,但
at() 函数会检查容器的边界,并在您尝试访问这些边界之外的内存时抛出运行时异常。 [] 运算符和 at() 成员函数执行相同的工作;它们根据容器元素的索引位置提供对容器元素的直接访问。 [] 运算符无需任何边界检查即可完成此操作,因此在某些迭代密集型应用程序中,它可能会稍微快一点。
好了,不说废话了:对于那些可以完全保证不会越界的代码段,使用[]无可厚非。但是笔者的态度是:总有人喜欢在酒吧里点炒饭,或者是-1杯啤酒,不要小看输入的多样性。所以,at() 函数应该是您的默认选择。
写过汇编的朋友都知道,比较两个数只需要几个CPU周期(啊哈!咱们不谈分支预测的事情,但是类似likely的操作总是好的,不是吗),但它是一种廉价的保险。对于大多数应用程序来说,其好处是值得的。
更加严肃的是,对于那些不允许因为内存overflow践踏而终止,或者希望程序自发的抛出异常传递而不是限制错误的编程理念的朋友更加应该使用at函数。他省去您做限制的心智负担。
相关文章:
C++ STL CookBook 6:STL Containers (I)
目录 顺序容器 关联容器 容器适配器 使用统一擦除函数从容器中删除指定项 在恒定时间内对一个对排序不敏感的vector中删除项目 如果不确定自己访问容器会不会越界,那就使用.at方法而不是[] 在我们开始之前,先来回顾一下传统的经典的几个容器&#…...
行转列实现方式总结
前言 在日常开发中遇到了,需要对表中数据某个字段行数据转成列,个人觉得这中做目前想到两种, 一种是sql 操作, 另一种代码中做逻辑处理。 方式一 Java 操作 import lombok.Data;import java.util.ArrayList; import java.util.H…...
【go从零单排】初探goroutine
🌈Don’t worry , just coding! 内耗与overthinking只会削弱你的精力,虚度你的光阴,每天迈出一小步,回头时发现已经走了很远。 📗概念 Goroutines 是 Go 语言中的一种轻量级线程,用于并发编程。它们允许程…...
HarmonyOS NEXT应用元服务开发Intents Kit(意图框架服务)本地搜索接入方案
一、方案概述 当用户使用应用/元服务时,开发者可以按照标准意图Schema向系统共享数据,并支持意图调用(空调用与传参调用),以实现用户点击卡片后,可后台执行功能(例如播放指定歌曲)或…...
C语言可变参数列表编程实战指南:从基础概念到高级应用的全面解析
引言 在C语言中,可变参数列表的功能使得函数能够灵活地处理不确定数量的输入参数。本文将深入探讨可变参数列表的基础概念、技术原理及其在实际编程中的应用,帮助开发者更好地理解和使用这一特性。 一、可变参数列表的基本概念 1.1 什么是可变参数列表…...
AndroidStudio-文本显示
一、设置文本的内容 1.方式: (1)在XML文件中通过属性:android:text设置文本 例如: <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.andr…...
HBuilderX运行微信小程序,编译的文件在哪,怎么运行
1. 点击HBuilderX顶部的运行-运行到小程序模拟器-微信开发者工具,就会开始编译 2. 编译完成后的文件在根目录找到 unpackage -- dist -- dev -- mp-weixin, 这里面就是编译后的文件,如果未跳转到开发者工具,那可能是没设置启动路径࿰…...
百亿AI数字人社会初现:Project Sid展示智能代理文明进化路径
项目背景 Project Sid 是一项开创性的AI代理人文明实验,旨在通过新开发的认知架构 PIANO 探讨AI代理人是否能够在大规模数字社会中实现文明的演进。这项实验不仅展示了社会进步、角色分化、治理体系及文化传播等特征,还揭示了一个包含百亿“数字人类”的社会可能性。 PIANO…...
代码随想录训练营Day21 | 491.递增子序列 - 46.全排列 - 47.全排列 II - 332.重新安排行程 - 51.N皇后 - 37.解数独
491.递增子序列 题目链接:491.递增子序列思路:和子集那道题思路很像,每次在数组中选择一个数,选过的数不能选择,这里要求集合数量必须大于2个才能符合,仍然需要去重,但这里选额的是子序列&…...
多用户商城系统的功能及设计和开发
多用户商城系统的功能及设计与开发(基于 PHP MySQL) 在现代电子商务平台的开发中,PHP MySQL 是一对非常流行且高效的技术栈。PHP作为服务器端脚本语言,结合MySQL数据库,可以高效地处理多用户商城系统的各种需求。本…...
2024年11月8日day8
半加器和全加器的区别 半加器:只能处理两个二进制位的相加,无法处理进位。全加器:不仅能处理两个二进制位的相加,还能处理来自低位的进位。 ⑴ 完成满足754标准存储格式的浮点数((43940000)16的十进制数值)…...
Debezium系列之:Debezium3版本增量快照和只读增量快照应用的变化
Debezium系列之:Debezium3版本增量快照和只读增量快照应用的变化 一、需求背景二、基于数据库信号表使用增量快照案例三、基于Kafka信号Topic使用增量快照案例四、只读增量快照案例五、增量快照技术总结增量快照相关知识请阅读博主下面系列文章: Debezium系列之:实现增量快照…...
Python正则表达式1 re.match惰性匹配详解案例
点个关注 re.match() re.match() 函数尝试从字符串的开头开始匹配一个模式,如果匹配成功,返回一个匹配成功的对象,否则返回None。大小写区分,内容匹配不到后面的,只能匹配一个,不能有空格(开头匹配&#…...
WPF(C#)学习日志10:Prism框架下按键绑定
在Prism框架下,提供了DelegateCommand类用于处理了UI的按键请求,XAML中可以直接采用 Command"{Binding **}" 来绑定这些方法。这个类是一个泛型的类生命时仅需要DelegateCommand<T>即可,同时在XAML中绑定CommandParameter&qu…...
WPF中的ResizeMode
在 WPF (Windows Presentation Foundation) 中,ResizeMode 属性用于指定窗口是否可以被用户调整大小,以及如何调整大小。ResizeMode 属性可以设置为以下几个值之一: NoResize:窗口不能被用户调整大小,但可以被程序代码…...
Unity3D UI 双击和长按
Unity3D 实现 UI 元素双击和长按功能。 UI 双击和长按 上一篇文章实现了拖拽接口,这篇文章来实现 UI 的双击和长按。 双击 创建脚本 UIDoubleClick.cs,创建一个 Image,并把脚本挂载到它身上。 在脚本中,继承 IPointerClickHa…...
LabVIEW扫描探针显微镜系统
开发了一套基于LabVIEW软件开发的扫描探针显微镜系统。该系统专为微观尺度材料的热性能测量而设计,特别适用于纳米材料如石墨烯、碳纳米管等的研究。系统通过LabVIEW编程实现高精度的表面形貌和热性能测量,广泛应用于科研和工业领域。 项目背景 随着纳…...
问题式教学法在生物教学中的应用探索
问题式教学法在生物教学中的应用探索 李新 山东省德州市平原县第五中学 山东 德州 253100 摘要:时代在发展教育事业也在不断进步,不断创新教学方法有利于提高教学质量。问题教学法能让教材知识点以问题的形式呈现在学生眼前,这对引导学生…...
C++ | Leetcode C++题解之第556题下一个更大元素III
题目: 题解: class Solution { public:int nextGreaterElement(int n) {int x n, cnt 1;for (; x > 10 && x / 10 % 10 > x % 10; x / 10) {cnt;}x / 10;if (x 0) {return -1;}int targetDigit x % 10;int x2 n, cnt2 0;for (; x2 …...
实现链式结构二叉树
目录 需要实现的操作 链式结构二叉树实现 结点的创建 前序遍历 中序遍历 后序遍历 计算结点个数 计算二叉树的叶子结点个数 计算二叉树第k层结点个数 计算二叉树的深度 查找值为x的结点 销毁 层序遍历 判断是否为完全二叉树 总结 需要实现的操作 //前序遍历 void …...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
