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 …...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...