当前位置: 首页 > news >正文

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中删除项目 如果不确定自己访问容器会不会越界&#xff0c;那就使用.at方法而不是[] 在我们开始之前&#xff0c;先来回顾一下传统的经典的几个容器&#…...

行转列实现方式总结

前言 在日常开发中遇到了&#xff0c;需要对表中数据某个字段行数据转成列&#xff0c;个人觉得这中做目前想到两种&#xff0c; 一种是sql 操作&#xff0c; 另一种代码中做逻辑处理。 方式一 Java 操作 import lombok.Data;import java.util.ArrayList; import java.util.H…...

【go从零单排】初探goroutine

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 Goroutines 是 Go 语言中的一种轻量级线程&#xff0c;用于并发编程。它们允许程…...

HarmonyOS NEXT应用元服务开发Intents Kit(意图框架服务)本地搜索接入方案

一、方案概述 当用户使用应用/元服务时&#xff0c;开发者可以按照标准意图Schema向系统共享数据&#xff0c;并支持意图调用&#xff08;空调用与传参调用&#xff09;&#xff0c;以实现用户点击卡片后&#xff0c;可后台执行功能&#xff08;例如播放指定歌曲&#xff09;或…...

C语言可变参数列表编程实战指南:从基础概念到高级应用的全面解析

引言 在C语言中&#xff0c;可变参数列表的功能使得函数能够灵活地处理不确定数量的输入参数。本文将深入探讨可变参数列表的基础概念、技术原理及其在实际编程中的应用&#xff0c;帮助开发者更好地理解和使用这一特性。 一、可变参数列表的基本概念 1.1 什么是可变参数列表…...

AndroidStudio-文本显示

一、设置文本的内容 1.方式&#xff1a; &#xff08;1&#xff09;在XML文件中通过属性&#xff1a;android:text设置文本 例如&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.andr…...

HBuilderX运行微信小程序,编译的文件在哪,怎么运行

1. 点击HBuilderX顶部的运行-运行到小程序模拟器-微信开发者工具&#xff0c;就会开始编译 2. 编译完成后的文件在根目录找到 unpackage -- dist -- dev -- mp-weixin, 这里面就是编译后的文件&#xff0c;如果未跳转到开发者工具&#xff0c;那可能是没设置启动路径&#xff0…...

百亿AI数字人社会初现:Project Sid展示智能代理文明进化路径

项目背景 Project Sid 是一项开创性的AI代理人文明实验,旨在通过新开发的认知架构 PIANO 探讨AI代理人是否能够在大规模数字社会中实现文明的演进。这项实验不仅展示了社会进步、角色分化、治理体系及文化传播等特征,还揭示了一个包含百亿“数字人类”的社会可能性。 PIANO…...

代码随想录训练营Day21 | 491.递增子序列 - 46.全排列 - 47.全排列 II - 332.重新安排行程 - 51.N皇后 - 37.解数独

491.递增子序列 题目链接&#xff1a;491.递增子序列思路&#xff1a;和子集那道题思路很像&#xff0c;每次在数组中选择一个数&#xff0c;选过的数不能选择&#xff0c;这里要求集合数量必须大于2个才能符合&#xff0c;仍然需要去重&#xff0c;但这里选额的是子序列&…...

多用户商城系统的功能及设计和开发

多用户商城系统的功能及设计与开发&#xff08;基于 PHP MySQL&#xff09; 在现代电子商务平台的开发中&#xff0c;PHP MySQL 是一对非常流行且高效的技术栈。PHP作为服务器端脚本语言&#xff0c;结合MySQL数据库&#xff0c;可以高效地处理多用户商城系统的各种需求。本…...

2024年11月8日day8

半加器和全加器的区别 半加器&#xff1a;只能处理两个二进制位的相加&#xff0c;无法处理进位。全加器&#xff1a;不仅能处理两个二进制位的相加&#xff0c;还能处理来自低位的进位。 ⑴ 完成满足754标准存储格式的浮点数&#xff08;(43940000)16的十进制数值&#xff09…...

Debezium系列之:Debezium3版本增量快照和只读增量快照应用的变化

Debezium系列之:Debezium3版本增量快照和只读增量快照应用的变化 一、需求背景二、基于数据库信号表使用增量快照案例三、基于Kafka信号Topic使用增量快照案例四、只读增量快照案例五、增量快照技术总结增量快照相关知识请阅读博主下面系列文章: Debezium系列之:实现增量快照…...

Python正则表达式1 re.match惰性匹配详解案例

点个关注 re.match() re.match() 函数尝试从字符串的开头开始匹配一个模式&#xff0c;如果匹配成功&#xff0c;返回一个匹配成功的对象&#xff0c;否则返回None。大小写区分&#xff0c;内容匹配不到后面的,只能匹配一个&#xff0c;不能有空格&#xff08;开头匹配&#…...

WPF(C#)学习日志10:Prism框架下按键绑定

在Prism框架下&#xff0c;提供了DelegateCommand类用于处理了UI的按键请求&#xff0c;XAML中可以直接采用 Command"{Binding **}" 来绑定这些方法。这个类是一个泛型的类生命时仅需要DelegateCommand<T>即可&#xff0c;同时在XAML中绑定CommandParameter&qu…...

WPF中的ResizeMode

在 WPF (Windows Presentation Foundation) 中&#xff0c;ResizeMode 属性用于指定窗口是否可以被用户调整大小&#xff0c;以及如何调整大小。ResizeMode 属性可以设置为以下几个值之一&#xff1a; NoResize&#xff1a;窗口不能被用户调整大小&#xff0c;但可以被程序代码…...

Unity3D UI 双击和长按

Unity3D 实现 UI 元素双击和长按功能。 UI 双击和长按 上一篇文章实现了拖拽接口&#xff0c;这篇文章来实现 UI 的双击和长按。 双击 创建脚本 UIDoubleClick.cs&#xff0c;创建一个 Image&#xff0c;并把脚本挂载到它身上。 在脚本中&#xff0c;继承 IPointerClickHa…...

LabVIEW扫描探针显微镜系统

开发了一套基于LabVIEW软件开发的扫描探针显微镜系统。该系统专为微观尺度材料的热性能测量而设计&#xff0c;特别适用于纳米材料如石墨烯、碳纳米管等的研究。系统通过LabVIEW编程实现高精度的表面形貌和热性能测量&#xff0c;广泛应用于科研和工业领域。 项目背景 随着纳…...

问题式教学法在生物教学中的应用探索

问题式教学法在生物教学中的应用探索 李新 山东省德州市平原县第五中学 山东 德州 253100 摘要&#xff1a;时代在发展教育事业也在不断进步&#xff0c;不断创新教学方法有利于提高教学质量。问题教学法能让教材知识点以问题的形式呈现在学生眼前&#xff0c;这对引导学生…...

C++ | Leetcode C++题解之第556题下一个更大元素III

题目&#xff1a; 题解&#xff1a; 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 …...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

C++使用 new 来创建动态数组

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