一 STL 组成

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

二 常用容器


容器简介

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

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 中作为 stackqueue 底层实现。其优势为:

    劣势为:不适合遍历,因为在遍历时,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 LRA[输入迭代器]B[输出迭代器]C[前向迭代器]D[双向迭代器]E[随机访问迭代器]

A --> CB --> CC --> DD --> 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 Node;typedef ListIterator 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* 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 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 Node;typedef HashTable _HashTable;typedef HashIterator 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 _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 <endl;  pq.pop(); }}

输出:

9752

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

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

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

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

template<class T>struct MyGreater : public binary_functionbool>{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

关注公众号 不会编程的程序圆,看更多干货

C++ 学习代码仓库:https://github.com/hairrrrr/Cpp-Primer

C 语言学习代码仓库:https://github.com/hairrrrr/C-CrashCourse

公众号ID:不会编程的程序圆学习编程,看更多干货 你们的在看就是对程序圆最大的鼓励!

stl 基于哈希的map c++_【C++】一文带你入门 STL相关推荐

  1. stl 基于哈希的map c++_关于哈希表,你该了解这些!

    (给算法爱好者加星标,修炼编程内功) 来源:代码随想录(本文来自作者投稿) 哈希表 首先什么是 哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道 ...

  2. BCrypt加密怎么存入数据库_松哥手把手带你入门 Spring Security,别再问密码怎么解密了...

    因为之前有小伙伴在松哥群里讨论如何给微人事的密码解密,我看到聊天记录后就惊呆了. 无论如何我也得写一篇文章,带大家入门 Spring Security!当我们在一个项目中引入 Spring Secur ...

  3. ntnub原理怎么看_老电工由浅入深带你入门学PLC的工作原理和梯形图的编程规则...

    PLC编程怎么学?很难吗?工控小白怎么入门学习PLC?需要为学习PLC编程做哪些准备?学习PLC编程时,前期一定要积累相关的理论知识,有了一定的基础,基础打扎实之后就是多练习了.今天推荐的重点,是关于 ...

  4. seo技术_基础知识_网站pr值的意义_日思663.带你入门SEO基础知识

    2019/9/6 这篇文章来自36氪产品团队一次内部分享,按照惯例记录下来,也分享给大家~ 本文937字,阅读约9分钟 从上上周开始,产品团队每周都会请一位小伙伴给大家做分享,主题不限,以自己擅长或感 ...

  5. sql查询每科成绩的最高分_数据分析SQL查询:一文带你入门到掌握

    [背景介绍] 在一家知名电商企业的BI部门实习四个多月,岗位为数据分析.日常工作中打交道最多的就是SQL和EXCEL,在实习之前SQL技能只会简单的增删改查语句,第一周实习经理甩了一份业务常见绩效取数 ...

  6. notebook pip install 只有星号_每日一点,带你入门Python-星号拆包

    星号(*)解包 今天我们来说说在python中经常使用的解包语法,这是python简洁语法的体现之一.在日常处理集合数据时非常有用.按照惯例,我们使用一个个尽可能小的例子来说明. 序列解包到变量 nu ...

  7. java etl工具_一文带你入门ETL工具-datax的简单使用

    什么是ETL? ETL负责将分布的.异构数据源中的数据如关系数据.平面数据文件等抽取到临时中间层后进行清洗.转换.集成,最后加载到数据仓库或数据集市中,成为联机分析处理.数据挖掘的基础. ETL是数据 ...

  8. HashMap - 基于哈希表和 Map 接口的键值对利器 (JDK 1.7)

    HashMap 的一些整理: (JDK 1.7) 基于哈希表的Map接口的非同步实现,定义了键映射到值的规则 此实现提供所有可选的映射操作,并允许使用null值和null键 此实现假定哈希函数将元素适 ...

  9. mysql做kv数据库_从零开始写KV数据库:基于哈希索引

    前言 新的KV数据库层出不穷,我们经常听说的KV数据库如RocksDb.Hbase等都是基于日志结构的存储引擎.最近我在看<数据密集型应用系统设计>,里面有一章专门在讲日志结构的存储引擎的 ...

最新文章

  1. Nature子刊:微生物来源分析包SourceTracker——结果解读和使用教程
  2. java 初始化duration_Java 8-Duration 详解
  3. pl/sql developer导入导出
  4. rsync 未授权访问漏洞
  5. 深大计算机科学与技术在广东省,广东考生请注意:深圳大学2021年计划本省总招生人数比例超过75%!...
  6. I love exam HDU - 6968
  7. 【POJ - 1330】Nearest Common Ancestors(lca,模板题)
  8. 简单理解极大似然估计MLE
  9. sap 打印预览界面点击打印时记录打印次数_9个Excel打印神技巧!从此打印不求人!...
  10. 关于天猫魔盒tmb100系列 开机灯亮显示器无反应的问题分析
  11. IBM的量子云计算准备开始商用 我国此前已实现量子加密产品商用
  12. 苹果7pnfc功能门禁卡_苹果手机怎么刷门禁卡?iPhone刷门禁卡的设置方法
  13. Linux 3g上网卡 拨号,Linux 系统 CDMA 无线上网卡拨号过程
  14. 白菜萝卜的做法 - 凉拌菜
  15. K64 计算 UART波特率
  16. 2022可用的免费天气预报API接口
  17. 云南高性能云桌面搭建解决方案、云桌面与传统PC优势对比,云桌面适用场景分析
  18. MBIST --- PATR1.Memorybist测试原理
  19. R学习笔记1:导入导出与变量清理
  20. 基于STM32F103C8T6四路AD采集数据显示在oled屏上非DMA传输方式(附百度网盘下载链接)

热门文章

  1. 【NOI2002】贪吃的九头龙
  2. 客户端相关知识学习(五)之什么是webView
  3. Spring MVC -- 国际化
  4. 如何迁移完整SQL数据库到另外一台服务器
  5. 随笔分类 - java高级特性
  6. 微信小程序-学习笔记6-组件
  7. iOS Safari 中click点击事件失效的解决办法
  8. python3 logging模块
  9. PDF文件上载图标,与启用浏览器浏览允许后依然无法在浏览器打开PDF文件的解决方案...
  10. jquery笔记(常用技术)