【C++ 笔记五】STL 标准模板库 —— 容器基础进阶

news2025/2/12 15:24:49

【C++ 笔记五】STL 标准模板库 —— 容器基础进阶


文接上文 【C++ 笔记四】STL 标准模板库 —— 容器基础

文章目录

  • 【C++ 笔记五】STL 标准模板库 —— 容器基础进阶
    • I - 简单回顾
      • 1.1 - 序列式容器(顺序容器)
      • 1.2 - 关联式容器 (关联容器)
      • 1.3 - 访问方法/对外接口
      • 1.4 - 迭代器
    • II - 容器概述与注意事项
      • 2.1 - 时间复杂度
      • 2.2 - 注意事项
        • 2.2.1 - 前递增/递减操作更高效
        • 2.2.2 - 基于范围的 for 循环 (range-based for loop)
        • 2.2.3 - list 不能使用 std::sort
    • III - Traits 方法
    • IV - 其他注意事项
    • V - 参考书目

I - 简单回顾


STL 六大组件之间的关系:

容器存储数据,存储需要使用内存,容器使用分配器去分配和释放内存,算法通过迭代器访问容器中的数据,仿函数用于算法的特别处理,适配器帮助仿函数/迭代器完成一些更细节的设置等。

1.1 - 序列式容器(顺序容器)


Sequence containers

顺序容器根据其内存结构可以大致分为三种

  • 数组,连续内存空间
  • 链表,非连续内存空间
  • 映射表管理的分段连续空间

在这里插入图片描述
array / vector 连续内存空间,支持随机访问。

注: Random Access,个人感觉最初的翻译不恰当,应翻译为 任意访问,random 本身也有任意的意思。随机有被动的意思,任意有更多主动的含义。

list / forward-list 支持双/单向顺序访问。

forward-list 相较 list 少一倍的指针,只支持单向的顺序访问,其的迭代器也不支持 - - 递减运算符和reverse_iterator, 与手写最好单向链表相当的性能,所以也不支持 size() 操作。

stackqueue 底层是 deque 映射表管理多端连续内存,为容器适配器不提供迭代器访问。

选用容器时,通常最佳选用 vector,除非中间插入删除操作非常多时使用 list, forward-list,通常相同元素数量情况下,后两者内存额外开销较大。

1.2 - 关联式容器 (关联容器)


Associative containers

在大部分书籍中,不定序容器(unordered container)也归类于关联式容器。关联式容器底层实现主要包含两种数据结构:

  • 红黑树
  • 哈希表

在这里插入图片描述
查找速度:哈希表 > 红黑树

哈希表 bucket_size 与元素数量相同时,会两倍扩容,使用新的哈希计算方式,元素会被重新打散,分布到新的 bucket 后。哈希查找 O(1),产生哈希冲突,即哈希值相同时只能循序查找。

1.3 - 访问方法/对外接口


  • 插入
push_back(elem);
emplace_back(elem);
push_front(elem);
emplace_front(elem);
  • 访问
// 若不存在,行为未定义
container[n]; 
// 若不存在,抛出异常 out_of_range
container.at(n); 

行为未定义,表示可能获取到一个意外的数据,或者程序异常退出等。

  • 删除
pop_back();
pop_front();

// 注意删除时刷新迭代器
erase(iter);
erase(iterA, iterB);

// 清空
clear();

删除示例:

std::vector<int> vec = { 1, 2, 4, 5, 6, 9 };

// lambda 表达式 [捕获](入参) -> 返回值类型 { 函数体 };
auto delete_element = [&vec](int a) -> bool 
{
	bool deleted(false);
	for (auto iter = vec.begin();
	iter != vec.end(); ++iter)
	{
		if (a == (*iter))
		{
			// 删除时刷新迭代器
			iter = vec.erase(iter);
			deleted = true;
		}
	}
	return deleted;
};

delete_element(5);

1.4 - 迭代器


迭代器为一种泛化的指针,容器内的可访问范围为一种左闭合区间 (left-inclusive interval) ,左闭右开 [begin, end) 。

在这里插入图片描述
反向迭代器的递增/递减 (++/--) 操作与正向迭代器方向相反。

II - 容器概述与注意事项

2.1 - 时间复杂度


顺序容器

顺序容器的查找时间复杂度为 O(n),即在存在 n 个元素时,大 O 表示时间渐进复杂度上界,也就是最差的情况下需要查找 n 次。

关联容器

红黑树为一种特殊的平衡二叉搜索树 (balanced binary search tree),查找复杂度为 O( l o g n log_{}n logn)。

  • 二叉树:一棵树型数据结构,每个节点至多有两个子节点。

  • 平衡二叉树:即对于树中任何一个节点,它的左子树和右子树的高度之差 (平衡因子) 的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。
    这样平衡二叉树就可以避免其中一棵子树过深,造成搜索时间过长。

  • 二叉搜索树:即对于树中任何一个节点,它左子节点的值,小于该节点的值。且该节点的值小于其右子节点的值。

红黑树如下图所示:
在这里插入图片描述
红黑树的最左节点为其最小值,最右节点为其最大值。

O( l o g n log_{}n logn) 推导过程:

因为每次查找从根节点开始,每次查找下降一个高度, 元素即节点为 n 的情况下,高度约为 l o g 2 n + 1 log_{2}n+1 log2n+1 ,也就是约需要查找 l o g 2 n + 1 log_{2}n+1 log2n+1 次。算法复杂度只保留最高阶即 l o g 2 n log_{2}n log2n,根据定义,

对于 O(g(n)) = f(n) ,存在正整数 n 0 n_{0} n0 和 c, 在 n ≥   n 0 n \geq\ n_{0} n n0 时 , 0 ≤   f ( n ) ≤   c g ( n ) 0 \leq\ f(n) \leq\ cg(n) 0 f(n) cg(n) 始终成立。

l o g 2 n log_{2}n log2n, 假定 a 为 0 到正无穷,不为 0 且不为 1 的常数, 根据换底公式

l o g 2 n = l o g a n l o g a 2 = 1 l o g a 2 ∗ l o g a n log_{2}n = \frac {log_{a}n} {log_{a}2} = \frac {1} {log_{a}2} * log_{a}n log2n=loga2logan=loga21logan

1 l o g a 2 \frac {1} {log_{a}2} loga21 可视为常数,可见底数为 2 或任何对于 log 有意义的底数都作用不大,所以 2 省掉即为
l o g n log_{}n logn

2.2 - 注意事项

2.2.1 - 前递增/递减操作更高效


前递增/递减的运算结果为左值,后递增/递减的运算结果为右值。因此可进行连续的前递增/减操作,连续的后递增/减操作会产生编译错误。

int i = 0;

++++i; // ok
i++;   // ok
i++++; // compilation error, need l-value

----i; // ok
i--;   // ok
i----; // compilation error, need l-value

编译错误为 需要一个左值。左值与右值,简而言之,对于一个赋值运算,等号左边的值为左值,等号右边的值为右值。

左值与右值的主要不同为,右值不可修改不可取地址。使用左值一般意义是使用其内存即地址值,右值一般使用其字面值即数据值,举例

int a(2), b(3);
a = 4; // 1
a = b; // 2
  • 1 处 4 为右值,使用其数据值,a 在此处为左值,使用其存储空间即地址值
  • 2 处 b 虽为与 a 相同的变量,但这里为右值,使用其数据值

容器迭代器实现前后递增操作符重载的方式是,后递增递减增加一个 int 参数以区分,但此参数无意义,仅用于区分。

list 的前递增与后递增代码示例:

// 前 ++,node 指针指向其下一个节点,且此处返回引用,即自身。如 cout << 可实现级联式输出操作,原理一致。
self& operator++()
{ node = (link_type) ((*node).next); return *this; }

// 后 ++ ,使用 int 参数区分,但本身此 int 无实际意义。
self operator++(int)
{ self tmp = *this; ++*this; return tmp; }
// 调用前 ++ ,并返回一个临时变量。

list 的后递增操作调用了前递增,由此也可得到前递增操作更高效。

2.2.2 - 基于范围的 for 循环 (range-based for loop)


C++11 为容器增加了基于范围的 for 循环语法,只能够访问和修改容器,但不能够在循环体内对容器进行增/删元素,否则会出现未定义行为 (undefined behavior)

for (auto decl : coll) 
{
	// loop body
}

// 例,访问
std::vector<double> vec;
for (auto elem : vec) { cout << elem << endl; }
// 修改
for (auto & elem : vec) { elem *= 3; }
// 添加元素 undefined behavior
std::vector<int> vec = { 1, 2, 4, 6 };
for (auto temp : vec) {
	if (4 == temp)
		vec.push_back(5);

	std::cout << temp << " "; // 1 2 4 -572662307
}

由于,上述语法在进入循环体之前,容器尾迭代器已经为确定的值,等效代码如下:

auto &&__coll = coll;
for (auto __begin = std::begin(__coll), __end = std::end(__coll);
	__begin != __end; ++__begin)
	{
		auto decl = *__begin;
		// loop body
	}

附加 cppreference 链接 :https://en.cppreference.com/w/cpp/language/range-for

2.2.3 - list 不能使用 std::sort


std::list<int> lst = { 5, 6, 10, 1, 4, 8 };
std::sort(lst.begin(), lst.end(), greater<int>()); // (a)
std::sort(lst.begin(), lst.end(), 
[](int a, int b)-> bool { return a > b; }); // (b)

首先,此处代码无法编译通过,因为 std::sort() 入参需要随机访问迭代器,而 list 的迭代器为双向顺序访问迭代器。list 排序只能使用自身类的 sort() 方法。

lambda 表达式可以做到与仿函数一样的效果。(a) 与 (b) 等价。

有些初学者分不清楚, std::less<T>()std::greater<T>() 哪个是从大到小,哪个是从小到大排列,这里有个记忆方法,greater 意为大于,大于号,也就是说此排序方式,使得容器中任意两个相邻元素之间大于号都成立,即 elem1 > elem2 > elem3 …,即为递减排列。less 同理。

III - Traits 方法


算法为了能够更好的适配容器,提高性能,需要了解迭代器的类型。

函数模板,编译器实参推导 (argument deduction),可以结合函数重载和静态多态来理解。

Traits 方法使用了模板函数偏特化的方式来萃取迭代器类型,理解 Traits 方法,是能看懂 STL 源码的重要一环。

// 范化
template <class type>
struct __type_traits {
    // ...
}
// 特化 Specialization 特化即为特殊处理
template<> struct __type_traits<int> 
{
    //...
}


typedef template<> __STL_TEMPLATE_NULL
    

// 偏特化 Partial Specialization 即为部分特化,部分特殊处理。
template<class Alloc>
class vector<bool, Alloc>
{
    //...
}

算法为了更好的适配迭代器,需要知道迭代器类型,每个STL迭代器都定义了五种类型,迭代器分类,值类型,首尾迭代器差值类型,指针类型,引用类型。

STL 加入中间层 Traits 使用模板偏特化为 原生指针封装此五种类型。

template <class I>
struct iterator_traits {
	typedef typename I::iterator_category iterator_category;
	typedef typename I::value_type        value_type;
	typedef typename I::difference_type   difference_type;
	typedef typename I::pointer           pointer;
	typedef typename I::reference         reference;
};

// pointer to T
template <class T>
struct iterator_traits<T*>
	typedef random_access_iterator_tag iterator_category;
	typedef T         value_type;
	typedef ptrdiff_t difference_type;
	typedef T*        pointer;
	typedef T&        reference;
};

// pointer to const T
template <class T>
struct iterator_traits<const T*>
	typedef random_access_iterator_tag iterator_category;
	typedef T         value_type;
	typedef ptrdiff_t difference_type;
	typedef T*        pointer;
	typedef T&        reference;
};
// 由于依然保留模板 所以为偏特化。

Traits 方法,也是为何 Qt 的容器也被 std 算法所支持的原因。

IV - 其他注意事项


push_backemplace_back 的区别。

emplace_back 是直接将构造函数所需的参数传递过去,然后构建一个新的对象出来,填充到容器尾部。也就是 就地构造(直接在容器内构造对象,不用拷贝一个复制品再使用)+ 强制类型转换 的方法来实现,在运行效率方面,由于省去了拷贝构造过程,因此效率更高。

V - 参考书目

  • 《STL源码剖析》— 侯捷
  • 《C++ Primer (中文版)》- 第 5 版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639835.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试-java常见问题

JVM 配置 程序计数器&#xff1a;当前线程所执行的字节码的行号指示器java虚拟机栈:临时变量元空间&#xff1a;类常量池&#xff0c;运行时常量池方法区&#xff1a;类信息&#xff0c;静态变量堆&#xff1a;对象实例&#xff0c;Sting常量池等 类加载过程 加载->链接&am…

使用javacv中的ffmpeg实现录屏

今天突发奇想&#xff0c;想自己写一个录屏的软件&#xff0c;上次写了一个专门录音的Demo&#xff0c;但是要把声音和视频放到一起合成一个mp4文件&#xff0c;着实有一点艰难&#xff0c;所以就打算使用ffmpeg来写一个&#xff0c;而这篇博客中会顺便谈一谈我碰到的各种坑。 …

JVM面试题50道

1.JDK、JRE、JVM关系&#xff1f; Jdk (Java Development Kit) : java语言的软件开发包。包括Java运行时环境Jre。 Jre &#xff08;Java Runtime Environment) :Java运行时环境&#xff0c;包括Jvm。 Jvm (Java Virtual Machine) :一种用于计算机设备的规范。 Java语言在不同…

JavaWeb小记——Tomcat

目录 Tomcat简介 Tomcat下载安装 Tomcat启动 Tomcat关闭 常见问题 项目发布 发布方式一 发布方式二 发布方式三 IDEA打war包 Tomcat和IDEA整合 IDEA发布动态项目 Tomcat简介 Tomcat是Apache基金组织下的一款免费的开源的且支持Servelet和JSP规范的服务器 Tomcat下…

Spark大数据处理学习笔记1.3 使用Scala集成开发环境

文章目录 一、学习目标二、搭建Scala的IntelliJ IDEA开发环境&#xff08;一&#xff09;启动IDEA&#xff08;二&#xff09;安装Scala插件&#xff08;三&#xff09;配置IDEA使用的默认JDK&#xff08;四&#xff09;创建Scala项目1、创建Scala项目 - ScalaDemo2、创建Scala…

跨平台潜能解锁:将Ionic框架与小程序容器相结合

Ionic是一个用于构建跨平台移动应用程序的开源框架。它结合了HTML、CSS和JavaScript等技术&#xff0c;帮助开发者创建具有原生应用体验的移动应用程序。Ionic提供了一套用户界面组件和工具&#xff0c;可用于构建高度交互和美观的移动应用界面。 Ionic基于Angular框架&#x…

为什么 Twitter 和 Facebook 的网站页面变得越来越像?

Twitter和Facebook这两个社交媒体平台在不同的领域取得了巨大的成功。Twitter以其独特的推文形式而闻名&#xff0c;而Facebook则以其广泛的社交网络和内容分享功能而著称。 然而&#xff0c;近年来&#xff0c;这两个平台在设计和布局上的相似之处越来越明显。为什么会出现这…

奖金高达534万!2023第四届全国人工智能大赛

2023第四届全国人工智能大赛 报名链接&#xff1a; https://www.datafountain.cn/special/NAIC2023?target13250069&specialNAIC2023 叮咚&#xff0c;已向您发送组队邀请&#xff01;今年最值得参与的第四届全国人工智能大赛开放报名了&#xff0c;3道赛题奖金534万&…

又一重点项目,石岩新能源产业园建面61.6万平,配27班学校

近日&#xff0c;宝安区城市更新和土地整备局发布&#xff0c;关于石岩街道总部经济园区城市更新单元(一期南及二期)“工业上楼”单元规划&#xff08;草案&#xff09;已通过专班会议审议的公告。 公告显示&#xff0c;项目申报主体为深圳市开宝安区投资管理集团有限公司&…

信息系统项目管理师(软考高项)备考总结

简介 信息系统项目管理师&#xff0c;计算机技术与软件&#xff08;高级&#xff09;专业技术资格。 相关考试简称软考&#xff0c;该资质业内简称高项。 证书价值 自行百度吧&#xff0c;决定考的肯定知道他能带来什么价值了。 笔者是因为从事软件开发&#xff0c;服务政…

js的一些工具函数以及方法

部分方法复制于&#xff1a;20 个 JS 工具函数助力高效开发 reduce 举例&#xff1a;数组求和 let a[1,3,6,5,7]; let init0;//累加的初始值&#xff0c;默认为0&#xff0c;可不写 let ba.reduce((pre,cur,index,arr)>{console.log(当前要加序号&#xff1a;,index);cons…

左右排版的PDF,如何转换为单栏排版的word?

将左右排版的PDF转换为单行排版的WORD文字版需要进行以下步骤&#xff1a; 1. 使用PDF转换工具将PDF转换为WORD格式。有很多在线或离线的PDF转WORD工具可供选择&#xff0c;例如金鸣表格文字识别、Adobe Acrobat、Smallpdf、Zamzar等。 2. 打开WORD文档后&#xff0c;选择“页…

24个Jvm面试题总结及答案

1.什么是Java虚拟机&#xff1f;为什么Java被称作是“平台无关的编程语言”&#xff1f; Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台&#xff0c;而不需要程序员为每…

用Python将《青花瓷》的歌词生成词云图

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 因为上次有小伙伴问我&#xff0c;歌曲的歌词和评论怎么生成词云图&#xff0c;想买代码… 当时我就拒绝了&#xff0c;直接免费送给了他。 所以今天来分享给大家 我们以周董的《青花瓷》为例&#xff0c;要对《青花瓷》歌词…

Nacos架构与原理 - 注册中心服务数据模型(2.x版本)

文章目录 服务&#xff08;Service&#xff09;和服务实例&#xff08;Instance&#xff09;定义服务服务元数据定义实例实例元数据持久化属性 集群&#xff08;Cluster&#xff09;定义集群 生命周期服务的⽣命周期实例的⽣命周期集群的⽣命周期元数据的⽣命周期 服务&#xf…

DM Ticket-大麦网自动购票工具 支持Docker一键部署

DM Ticket-大麦网自动购票工具 支持Docker一键部署 DM Ticket&#xff0c;一个大麦网演唱会自动购票工具&#xff0c;支持Docker一键部署&#xff0c;不过小白想要操作的话需要一点命令知识&#xff0c;作者的GitHub项目页面有很详细的介绍&#xff0c;感兴趣的同学可以到GitH…

反汇编分析——全局、局部、静态、堆变量

在可执行文件编译的时候就已经存储在固定的位置了&#xff0c;甚至还可以跨文件共享&#xff0c;因为他本身就是静态的&#xff0c;固定在文件当中的嘛 反汇编窗口就是直接拿指针解引用&#xff0c;也就是拿这个地址来访问的&#xff0c;直接寻址 自动变量&#xff0c;不用我们…

C# .NET ADO.NET介绍和如何使用

文章目录 环境配置ADO.NET简介ADO.NET是什么面向过程和面向对象什么是ORM ADO.NET用于解决什么问题优化开发效率对已存在的数据库&#xff0c;设计多个程序对开发中的程序&#xff0c;动态设计数据库&#xff0c;同步更新 ADO.NET如何使用&#xff0c;以sql server为例ADO.NET如…

多元回归预测 | Matlab哈里斯鹰算法(HHO)优化随机森林的数据回归预测,HHO-RF回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab哈里斯鹰算法(HHO)优化随机森林的数据回归预测,HHO-RF回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清…

IPV6综合实验

拓扑结构&#xff1a; 要求&#xff1a; 1、两个局域网基于6 to 4 tunnel可达&#xff0c;公网使用IPV4地址 2、R1可以访问R3的环回 3、保障网络更新安全&#xff0c;全网可达 使用的设备&#xff1a;8台路由器 解决网络拓扑&#xff1a; 1、确定广播域的个数 2、分配网段 …