【C++】一文带你入门 STL

news2024/12/23 11:41:06

一 STL 组成

graph LR
       A[STL] --- B[容器 container]
       A --- C[配接器 adapter]
       A --- D[迭代器 iterator]
       A --- E[仿函数 function]
       A --- F[算法 algorithm]
      A --- G[空间配置器 allocator]

二 常用容器

容器简介

下面我们来简单看一下这些容器的常用接口的使用,并分析其复杂度

vector

 

vector

list

deque

stack

queue

priority_queue

set & multiset

map & multimap

unordered_map & unordered_multimap

unordered_map
底层实现:动态数组 + 链表(红黑树)
查找:find()
计数:count()
指定插入:insert()
指定删除:erase()
随机访问operator[]

由于底层实现哈希表的时候,会有负载因子的存在,所以可以认为上述操作的复杂度都为 O(1)

如果哈希冲突比较多,可以采用 动态数组 + 红黑树实现,复杂度最多为 O(logn)

因此,哈希表的优势为 O(1) 的查找;这是一种以空间换时间的策略。

容器选择

  • 不知道使用什么容器时,优先选择 vector

  • 如果不需要频繁的任意位置插入,需要支持随机访问,选择 vector

  • 如果不需要随机访问,需要频繁的插入,选择 list

  • 如果需要频繁的对头/尾操作,选择 deque

  • 如果需要高效的查找,但对内存没有限制,可以选择 unordered_map ;如果对内存有限制,可以选择 set/map

  • deque 在 STL 中作为 stack 和 queue 底层实现。其优势为:

    劣势为:不适合遍历,因为在遍历时,deque 的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下

    • 相比于 vector :头部插入和删除以及扩容时,效率更高,因为不需要搬大量的元素

    • 相比于 list:底层空间更为连续,空间利用率更高

三 迭代器

1. 迭代器种类

迭代器种类一节摘自: Stl整理 (https://wu-kan.cn/_posts/2019-04-20-STL%E6%95%B4%E7%90%86/#%E7%AE%97%E6%B3%95%E6%A6%82%E8%A7%88)

C++中,一图解释五类迭代器之间的继承关系:

graph LR
A[输入迭代器]
B[输出迭代器]
C[前向迭代器]
D[双向迭代器]
E[随机访问迭代器]

A --> C
B --> C
C --> D
D --> E

输入迭代器(input iterator)

只读,不写;单遍扫描,只能递增。

输入迭代器只支持顺序访问,通常用于读取序列中的元素。对于一个输入迭代器it*it++保证是有效的,但递增它可能导致所有其他指向流的迭代器失效。其结果就是,不保证其状态可以保存下来并用来访问元素。因此,输入迭代器只能用于单遍扫描的算法。支持如下操作:

  • 相等(==)、不等(!=)比较。

  • 用于推进迭代器的前置和后置递增(++)。

  • 解引用(*),只能出现在赋值运算的右侧;箭头运算符(->),it->member等价于(*it).member

算法find要求输入迭代器;而类型istream_iterator是一种输入迭代器。

输出迭代器(output iterator)

只写,不读;单遍扫描,只能递增。可以看作输入迭代器功能上的补集。

只能向解引用的输出迭代器赋值一次,类似输入迭代器,输出迭代器也只能用于单遍扫描的算法,通常用作算法的目标位置。支持如下操作:

  • 用于推进迭代器的前置和后置递增(++)。

  • 解引用(*),只能出现在赋值运算的左侧。

算法fill要求输出迭代器;而类型ostream_iterator是一种输出迭代器。

前向迭代器(forward iterator)

可读写;多遍扫描,只能递增。

可以读写元素,只能在序列中沿着一个方向移动,支持所有输入迭代器和输出迭代器的操作,而且可以多次读写同一个元素。因此,可以保存前向迭代器的状态,使用前向迭代器的算法可以进行多遍扫描。

算法replace要求前向迭代器;容器forward_list上的迭代器是前向迭代器。

双向迭代器(bidrectional iterator)

可读写;多遍扫描,能递增递减。

可正向/反向读写序列中的元素。除支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置递减(--)。

算法reverse要求双向迭代器;除了forward_list之外,其他标准库容器上的迭代器都是双向迭代器。

随机访问迭代器(random-access iterator)

可读写;多遍扫描,支持全部迭代器运算。

提供在常数时间内访问序列中任意元素的能力,除支持双向迭代器的所有操作外,还支持:

  • 用于比较两个迭代器的相对位置(><>=<=)。

  • 迭代器和一个整数值的加减运算(++=--=),计算结果迭代器在序列中前进或后退给定整数后的迭代器。

  • 迭代器之间的减法(-),得到两个迭代器之间的距离。

  • 下标运算符([]),it[n]*(it+n)等价。

算法sort要求随机访问迭代器;容器vector上的迭代器和用于访问内置数组的指针是随机访问迭代器。

2. 迭代器失效

图片来源 cppreference

3. 迭代器实现

以下都是迭代器的简单实现,要看对容器的完整实现,请去我的 Github,链接如下:

https://github.com/hairrrrr/Cpp-Primer

记得留下你的 star 哟~

deque

 

简单实现:

template<class T,...>
struct __deque_iterator{
    ...
    T* cur;
    T* first;
    T* last;
    map_pointer node;//map_pointer 等价于 T**
}

//当迭代器处于当前连续空间边缘的位置时,如果继续遍历,就需要跳跃到其它的连续空间中,该函数可用来实现此功能
void set_node(map_pointer new_node){
    node = new_node;//记录新的连续空间在 map 数组中的位置
    first = *new_node; //更新 first 指针
    //更新 last 指针,difference_type(buffer_size())表示每段连续空间的长度
    last = first + difference_type(buffer_size());
}
//重载 * 运算符
reference operator*() const{return *cur;}
pointer operator->() const{return &(operator *());}
//重载前置 ++ 运算符
self & operator++(){
    ++cur;
    //处理 cur 处于连续空间边缘的特殊情况
    if(cur == last){
        //调用该函数,将迭代器跳跃到下一个连续空间中
        set_node(node+1);
        //对 cur 重新赋值
        cur = first;
    }
    return *this;
}
//重置前置 -- 运算符
self& operator--(){
    //如果 cur 位于连续空间边缘,则先将迭代器跳跃到前一个连续空间中
    if(cur == first){
        set_node(node-1);
        cur == last;
    }
    --cur;
    return *this;
}

list

/**
 * ListNode: 链表的节点类型
 */
template<class T>
struct ListNode
{
 T _value;
 ListNode* _next;
 ListNode* _prev;

 ListNode(const T& val = T())
  :_value(val)
  ,_next(nullptr)
  ,_prev(nullptr)
 {}
};


/**
 * ListIterator: 链表的迭代器
 * 类类型的迭代器不是指针,而是一个类。
 * 各种操作其实本质是操作 ListNode 中的某个成员
 */
template<class T, class Ref, class Ptr>
struct ListIterator
{
 typedef ListNode<T> Node;
 typedef ListIterator<T, Ref, Ptr> Self;

 ListIterator(Node* node)
  :_node(node)
 {}

 Ref operator*()
 {
  return _node->_value;
 }
 
 /** 
  * -> 当作单目运算符处理
  * 类类型中的元素访问模式:ListNode->_val->某一个元素
  * 中间的 ->_val 被编译器优化
  */
 Ptr operator->()
 {
  return &_node->_value;
 }

 Self& operator++()
 {
  _node = _node->_next;
  return *this;
 }

 Self& operator--()
 {
  _node = _node->_prev;
  return *this;
 }

 bool operator!=(const Self& it)
 {
  return _node != it._node;
 }

 Node* _node;
};

因为 map & set & multimap & multiset 底层都是用红黑树实现的,因此,我们有必要看一下红黑树的迭代器如何实现。

红黑树

template<class Value>
struct RBNode
{
 typedef RBNode<Value>* Ptr_RBNode;

 Value _data;
 Color _color;
 Ptr_RBNode _left;
 Ptr_RBNode _right;
 Ptr_RBNode _parent;

 RBNode(const Value& data = Value())
  :_data(data)
  ,_color(RED) // 节点颜色默认初始化为 red
  ,_left(nullptr)
  ,_right(nullptr)
  ,_parent(nullptr)
 {}
};

template<class Value>
struct RBIterator
{
 typedef RBNode<Value> Node;
 typedef RBIterator Self;

 Node* _node;

 RBIterator(Node* node)
  :_node(node)
 {}

 Value& operator*()
 {
  return _node->_data;
 }

 Value* operator->()
 {
  return &_node->_data;
 }

 bool operator==(const Self& it)
 {
  return _node == it._node;
 }

 bool operator!=(const Self& it)
 {
  return _node != it._node;
 }

 Self& operator++()
 {
  // 如果当前节点有右节点
  if (_node->_right)
  {
   _node = _node->_right;
   // 让 _node 设为最右节点
   while (_node->_left)
    _node = _node->_left;
  }
  // 向上回溯,如果当前节点为父节点的右节点,继续向上回溯
  else
  {
   Node* parent = _node->_parent;
   while (_node == parent->_right)
   {
    _node = parent;
    parent = parent->_parent;
   }
   // 当 parent 为 _header ,_node 为整个树的根节点时,
   // 一定会退出上面的 while 循环,此时应该将 _node 置为 _header
   // 如果 _node 不在父节点的左侧,也应该单独执行一次,将 _node 指向下一次访问的节点
   /*_node = parent;*/
   // 上面的写法有一个问题,如果整个树只有一个节点。_header->right == _node
   // _node 会走到 _header, parent 走到 _node 此时退出循环
   // _node 再被赋值为 _node , 所以迭代器的遍历会陷入循环
   if (_node->_right != parent)
    _node = parent;
  }

  return *this;
 }
};

map 和 set 的迭代器只是对红黑树的迭代器进行了封装,然后给出了 begin() 和 end() 方法。

哈希表

template<class K, class V, class keyOfValue, class HashFun>
struct HashIterator
{
 typedef HashNode<V> Node;
 typedef HashTable<K, V, keyOfValue, HashFun> _HashTable;
 typedef HashIterator<K, V, keyOfValue, HashFun> Self;

 Node* _node;
 _HashTable* _ht;

 HashIterator( Node* node,  _HashTable* ht)
  :_node(node)
  ,_ht(ht)
 {}

 V& operator*() const 
 {
  return _node->_value;
 }

 V* operator->() const
 {
  return &_node->_value;
 }

 bool operator!=(const Self& it) const
 {
  return _node != it._node;
 }

 Self& operator++()  
 {
  // 如果 _node._next 存在
  if (_node->_next != nullptr)
  {
   _node = _node->_next;
  }
  // 在哈希表中向后寻找非空链表
  else
  {
   keyOfValue kov;
   HashFun hf;

   int idx = hf(kov(_node->_value)) % _ht->_table.size();

   idx++;
   for (; idx < _ht->_table.size(); idx++)
   {
    if (_ht->_table[idx])
    {
     _node = _ht->_table[idx];
     break;
    }
   }
   // 哈希表已遍历完
   if (idx == _ht->_table.size())
    _node = nullptr;

   return *this;
  }
 }
};

四 仿函数

priority_queue 默认是一个“大根堆”,使用如下代码测试:

int main(void)
{
 priority_queue<int> pq;

 pq.push(9);
 pq.push(5);
 pq.push(2);
 pq.push(7);

 while (!pq.empty())
 {
  cout << pq.top() << endl;
  pq.pop();
 }
}

输出:

9
7
5
2

如果我们想要堆中最小的元素,也就是建立一个“小根堆”,可以通过这种方式定义 pq:

priority_queue<int, vector<int>, greater<int>> pq;

 

第二个参数表示 priority_queue 内部的实现方式,第三个参数定义了比较规则

我们也可以自己来定义比较规则:

template<class T>
struct MyGreater : public binary_function<T, T, bool>
{
 bool operator()(const T& lhs, const T& rhs)
 {
  return lhs > rhs;
 }
};

然后通过如下方式调用:

priority_queue<int, vector<int>, MyGreater<int>> pq;

五 空间配置器

未完待续。。。

六 算法

未完待续。。。

参考文章:

C++ STL deque容器底层实现原理(深度剖析)

http://c.biancheng.net/view/6908.html

竞赛常用STL容器详解

https://blog.csdn.net/weixin_43844677/article/details/104902417

Stl整理

https://wu-kan.cn/_posts/2019-04-20-STL%E6%95%B4%E7%90%86/#%E7%AE%97%E6%B3%95%E6%A6%82%E8%A7%88

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

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

相关文章

更新中-深度学习实战中遇到的一些概念+少量代码

onnx ONNX 是一种用于机器学习模型的开放式表示格式&#xff0c;它可以让不同的深度学习框架之间共享模型。 import onnxruntime # 加载模型 session onnxruntime.InferenceSession(model.onnx) # 运行模型。第一个参数是输出变量列表&#xff0c;不指定的话返回所有值 outp…

ESP8266使用MicroPython接入ThingsBoard

1、概述 我们老大当初叫我学习microPython,这个可以直接将代码发到板子上,然后就可以跑,就相当于设备业务代码由我们来写,不仅仅是让嵌入式来写,嵌入式做的就是封装函数,我们可以调用.最终这个还是实现了,但是没有推广. 2、设备 我自己购买的设备是ESP8266,某宝上购买的,mic…

智能仓储系统哪家公司做的比较好?求推荐排名不错的智能仓储公司?

什么是仓储服务信息平台&#xff1f;仓储服务信息平台可以为企业提供哪些便利&#xff1f; 随着电商和物流行业的快速发展&#xff0c;仓储服务越来越受到人们的关注。为了更好地管理仓储服务&#xff0c;提高效率&#xff0c;降低成本&#xff0c;仓储服务信息平台也应运而生…

CTF国赛2023 - ukfc(四道逆向已下班)

没啥好说的&#xff0c;惜败已复现&#xff1a;badkey1、国粹、ezbyte、moveAside、ezAndroid Notice&#xff1a;复现时候的一些题解来源于各大战队的wp&#xff0c;比如F61d&#xff0c;侵删 Re ezbyte 首先跟踪很容易分析到前后缀 至于里面的&#xff0c;得知道是dwarf…

哪个牌子的电容笔好用?Apple Pencil平替

随着时代的进步&#xff0c;数码产品在人们日常生活中的使用频率越来越高&#xff0c;一个iPad和一支电容笔似乎已然成为人们主要的学习工具了。电容笔的发展速度很快&#xff0c;在众多的电容笔牌子中&#xff0c;什么牌子好用又便宜&#xff1f;下面&#xff0c;我来给大家推…

深度学习进阶篇[8]:对抗神经网络GAN基本概念简介、纳什均衡、生成器判别器、解码编码器详解以及GAN应用场景

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

FastReport Business Graphics .NET 2023.1 Crack

FastReport 商业图形 .NET .NET WinForms 的数据可视化库 使用 FastReport 业务图形库&#xff0c;您可以可视化不同层次的数据&#xff0c;构建业务图表以供进一步分析和决策。 所有这些都可以直接在您的应用程序中工作&#xff01; 利用 .NET 7、.NET Core、Blazor、ASP.NE…

数据采集学习心得

数据采集是指从互联网或其他数据源中收集数据的过程。在当今信息时代&#xff0c;数据采集已经成为了一项非常重要的技能。无论是在商业领域还是学术领域&#xff0c;数据采集都是非常必要的。本文将分享我在学习数据采集过程中的心得体会。 一、数据采集的重要性 在当今信息…

高考即将到来,如何选择大学和专业?(2023版本)

同学们&#xff0c;高考又快到了&#xff0c;怎么选择大学和专业一直是同学们心烦的事情&#xff0c;正所谓“选择大于努力”&#xff0c;选择错了&#xff0c;努力方向自然就错了&#xff0c;事倍功半&#xff0c;甚至南辕北辙&#xff0c;所以对此我们必需慎之又慎&#xff0…

Mybatis自定义分页插件及PageHelper源码分析

文章目录 前言一、自定义一个简单的mybatis分页插件&#xff1f;1.判断当前传参是不是一个Page&#xff0c;如果是page就进行转换。2.分页查询总条数3.修改原有sql4.执行原有方法5.存在问题&#xff1a; 二、PageHelper分析1.PageHelper简介2.PageHelper源码分析 三&#xff1a…

概率论:假设检验

参考书目&#xff1a;《行为科学统计精要》&#xff08;第八版&#xff09;——弗雷德里克J格雷维特 1、假设检验预备知识 Z分位数样本均值的分布标准误 参考&#xff1a; 概率论&#xff1a;样本与总体分布&#xff0c;Z分数与概率_格勒王的博客-CSDN博客如何理解样本和整体…

Linux入门到进阶

文章目录 前言一、第一章-初识Linux1.初识Linux2.虚拟机介绍3.VMware Workstation安装4.在VMware上安装Linux5.远程连接Linux系统6.扩展&#xff1a;WSL&#xff08;Windows Subsystem for Linux&#xff09;7.扩展&#xff1a;虚拟机快照 二、第二章-Linux基础命令1.Linux的目…

AMEYA360:纳芯微推出车规级耐高压、三线霍尔开关及锁存器NSM101x系列

纳芯微推出全新三线制车规霍尔效应开关/锁存器NSM101x系列&#xff0c;为数字位置检测提供高精度的解决方案&#xff0c;可被广泛应用于汽车执行器等的位置检测。 NSM101x产品系列包含了3个产品型号&#xff0c;即NSM1011(单极霍尔开关)、NSM1012(全极霍尔开关)、NSM1013(霍尔锁…

oracle19c介绍和windows上安装

目录 一、版本 &#xff08;1&#xff09;历史 &#xff08;2&#xff09;11g和12c管理方式区别 11g 12C &#xff08;3&#xff09;各个版本对操作系统要求 二、分类 &#xff08;1&#xff09;分为桌面类和服务器类 &#xff08;2&#xff09;分为企业版和标准版 三…

后端(一):Tomcat

我们之前的前端是被我们一笔带过的&#xff0c;那不是我们要讲的重点&#xff0c;而这里的后端则是重点。本章先来认识认识后端的基础。 Tomcat 是什么 我们先来聊聊什么叫做tomcat&#xff0c;我们熟悉的那个是汤姆猫&#xff1a; 这和我们Java世界中的Tomcat 不是同一只猫&…

包含合并单元格的表格快速排序

实例需求&#xff1a;现需要将原料配方成分表按照“原料含量”从高到低排序&#xff0c;如下图所示。由于表格中包含合并单元格&#xff0c;因此Excel的排序功能无法正常。 示例代码如下。 Sub demo()Dim data(), i, idx, k, slstRow Cells(Rows.Count, 2).End(xlUp).RowReDi…

02-项目系统架构

1、为何选择xxxxx 1.1、高并发场景有哪些&#xff1f; 商品秒杀&#xff0c;双11 微信支付宝平台 微博突发热点 用户操作日志 购票平台 1.2、为何选择xxxxx 业务复杂度高于淘宝双11&#xff0c;考验个人程序设计能力 动态库存 选座功能 线上线下 持续高并发业务&…

5G宏基站的形态5G基站长什么样?

据说&#xff0c;全国建了约273.3万个5G基站。 真是春城无处不飞花&#xff0c;5G遍布千万家。 今天我们换个轻松的话题&#xff0c;来看看春光下的5G宏基站。 胜日寻芳泗水滨&#xff0c;无边光景一时新。 等闲识得东风面&#xff0c;万紫千红总是春。 古人在春游时寻芳&am…

QGIS 对 GeoJson 文件数据进行压缩

项目场景 地图开发&#xff0c;友商提供的是边界Shapefile文件&#xff0c;文件比较大&#xff0c;例如某个文件就 29.2 M &#xff0c;这么大的数据&#xff0c;在echarts 上显示地图&#xff0c;前端会很卡&#xff0c;特别是有地图下钻的时候&#xff0c;体验很不好&#x…

【终极计算平台】上海道宁为您提供​Wolfram技术,支持跨桌面、云、服务器和移动设备的强大工作流程

Wolfram帮助世界 加快研究、教育、技术发展和 革新的步伐 无论您所在任何领域 无论您需要任何应用 Wolfram技术都是您的终极计算平台 Mathematica 具有 涵盖所有技术计算领域的 将近 6,000 个内置函数—— 所有这些都经过精心制作 使其完美地 整合在Mathematica系统中…