STL 简单讲解

网上有很多很好的资料可以参考

而直接看标准是最准确清晰的

  • vector

    • stack
    • queue / priority_queue
    • deque
  • array
  • map / multimap
  • set / multiset
  • unordered_map
  • unordered_set
  • 关于指针和迭代器
  • !!! pbds
  • ……

本文默认认为读者会基本的 STL 应用。

一切 STL 从 \(0\) 开始左闭右开!

顺序容器

vector 是最常用的,动态数组。

  • vector<int> v(n, 0)
  • v.reserve(n) 预分配空间,加速 push_back
  • v.push_back(x)
  • v.clear() 只是移动指针,但是不会改变占用的内存!
  • v.shrink_to_fit() 缩减内存到恰好有 v.size() 个。

其常用的复杂度如下:

  • 随机访问:\(O(1)\)
  • 末尾删除或者加入元素:均摊 \(O(1)\)
  • 插入或删除一个元素:与到 vector 末尾的距离成线性 \(O(n)\)。

还有一些常用的成员函数:

  • v.at(i) 访问指定位置的元素,进行越界检查(聊胜于无)

  • v.front(), v.back() 访问开头/结尾的元素,在空容器上调用是未定义行为

  • v.empty() / v.size() / v.resize()

  • \(O(n)\) 删除/加入一个元素的写法,删除需要保证一定存在!

    v.erase(lower_bound(begin(v), end(v), x));
    v.insert(lower_bound(begin(v), end(v), x), x);
  • 离散化的操作? \(O(n \log n + n)\)

    sort(begin(v), end(v));
    v.erase(unique(begin(v), end(v)), end(v));

    begin(v)v.begin() 等价,end(v) 同理

deque 是神秘的双端队列。

与 std::vector 相反,deque 的元素不是相接存储的:典型实现用单独分配的固定尺寸数组的序列,外加额外的登记,这表示下标访问必须进行二次指针解引用,与之相比 vector 的下标访问只进行一次。

摘自 cppreference

也就是唯一与 vector 的区别在于可以 \(O(1)\) 的 push_front() / pop_front()

其他的东西和 vector 一模一样(只是常数比较大)。

stackqueue 以及 priority_queue 称为容器适配器。

该类模板表现为底层容器的包装器—,即只提供特定函数集合。

默认情况下,stackqueue 都是使用 deque 作为底层,所以常数很大。

一般来说,stack 应当用 vector 代替,queue 手写即可(除非不知道到底会有多少元素入队)。

priority_queue 底层默认是 vector,其常数仍然很大……但是还是有替代品(一般不需要)

algorithm 库中有 make_heap / push_heap / pop_heap / sort_heap 操作,但我声称没有必要。

还有三个存在感极差的顺序容器

  • array<T, N> 就是 STL 中的静态数组,一般用在套 vector 的情况:vector< array<int, 2> > f(n);
  • list / forward_list 可以用在邻接表中,但是不如用 vector 快。

在这些顺序容器上可以有一些神秘的 STL 操作:

#define all(x) begin(x), end(x)
  • auto it = find(all(v), x) \(O(n)\) 的在数组中寻找首个 \(x\) 的位置,无返回 end(v)
  • auto it = lower/upper_bound(all(v), x) \(O(\log n)\) 的在有序的数组中寻找 \(x\) 的位置。
  • int cnt = count(all(v), x) \(O(n)\) 的返回其中 \(x\) 的个数。
  • reverse(all(v)) \(O(n)\) 翻转。
  • merge(all(v1), all(v2), back_inserter(res)) \(O(n + m)\) 的归并两个有序数组
  • T res = max/min(all(v)) \(O(n)\) 的返回最大/最小元素。
  • pair<T, T> res = minmax(all(v)) \(O(n)\) 的返回一个 pair
  • iota(all(v), x) 循环赋值 \(v[i] = x + i\)。
  • fill(all(v), x) / fill_n(begin(v), siz, x) 赋值。
  • ……

关联容器

分为两大类:

  • 有序 mapsetmulti...

    • 红黑树
    • 都是 \(O(\log n)\) 单次操作
  • 无序 unordered_mapunoredered_setunordered_multi...
    • 哈希表
    • 平均 \(O(1)\),最坏 \(O(n)\)

有序容器上的操作

  • s.lower_bound(x) 才是 \(O(\log n)\) 的
  • s.find(x) 如果没找到返回的是 end(s)
  • multiset 上执行 count 操作是 \(O(\log n + s)\) 的,\(s\) 是元素的个数。

无序上的操作

  • s.reserve(n) 预留 \(n\) 个元素的空间,减少多次插入的时间。

迭代器

有四种迭代器,但是一般只会用到一种:正向迭代器

begin(x), end(x) 返回的就是指向容器开头,末尾的迭代器。

对于顺序容器,操作返回的迭代器为 随机访问迭代器,而关联容器(和 list)返回的则是 双向迭代器

对于随机访问迭代器,我们可以 it +/- x,而双向迭代器只能 ++it / --it

对于空迭代器(例如 end(v))的操作是未定义行为,可能 RE,也可能无事发生。

vector<int> v(10, -1);
iota(begin(v), begin(v) + 5, 0);
vector<int>::iterator it = find(begin(v), end(v), 4); // int* 也可以看作是随机访问迭代器
int a[100];
fill(a, a + 50, 7);
// for (int i = 0; i < 50; ++i) a[i] = 7;
int *it = lower_bound(a, a + 50, 8); // it == a + 50

失效的迭代器

写珂朵莉树或多或少都知道:

auto itr = split(r + 1), itl = split(l); // 顺序不能颠倒,否则可能RE

这是因为 split(r + 1) 操作可能影响到 l 所在的节点,导致迭代器失效。

不会修改容器的方法一定不会使迭代器或引用失效。修改容器内容的方法可能会使迭代器和/或引用失效。

对于 vector,后面的操作不会影响前面迭代器,如果容量变化也会失效。

vector<int> v(10);

auto it = begin(v) + 5;
v.insert(begin(v) + 7); // it 不失效
v.insert(begin(v)); // it 失效
v.resize(5) // it 失效

对于 deque,可以认为只要修改了内容迭代器就失效了。

对于 map, set, multi... 认为一直有效,除非本身被 erase 或容器被 clear

对于 unordered_... 也认为一直情况有效,除非插入导致重哈希。

STL 的字符串

读入一行:

string s;
cin >> std::ws;
getline(cin, s);

关于 std::string 的那些事……

可以 clear / insert / pop/push_back / erase / resize / reserve / ...,类似于 vector<char>

只是多了 s.substr(pos, len) 返回子串 \([pos, pos + len)\) 或者 \([pos, size())\)。

在 \(pos \gt size()\) 时会报错(RE),复杂度与 \(len\) 成线性。

如果不需要对原字符串进行修改,但是需要获取子串,推荐 string_view

string s = "ni hao guai er zi";
string_view v(s);
string_view sub = v.substr(3, 3); // sub == "hao", O(1);
sub.remove_prefix(1); // sub == "ao", O(1);
sub.remove_suffix(1); // sub == "a", O(1);

然而,我声称可以定义广义字符串:

basic_string<int> v;
for (int i = 0; i < 5; ++i) v += i;
// v = {0, 1, 2, 3, 4} /* 以下是 C++17 及以上的行为 */
basic_string_view<int> vi(v);
vi.remove_prefix(2);
for (int x : vi) cout << x << ' ';

C++ STL 容器简单讲解的更多相关文章

  1. 史上最全的各种C++ STL容器全解析

    史上最全的C++ STL 容器大礼包 为什么\(C++\)比\(C\)更受人欢迎呢?除了\(C++\) 的编译令人感到更舒适,\(C++\)的标准模板库(\(STL\))也占了很重要的原因.当你还在用 ...

  2. STL容器删除元素的陷阱

    今天看Scott Meyers大师的stl的用法,看到了我前段时间犯的一个错误,发现我写的代码和他提到错误代码几乎一模一样,有关stl容器删除元素的问题,错误的代码如下:std::vector< ...

  3. 标准C++中的STL容器类简单介绍

    SGI -- Silicon Graphics[Computer System] Inc.硅图[计算机系统]公司. STL -- Standard Template Library 标准模板库.   ...

  4. 不要在公共接口中传递STL容器

    最近的一个项目,是开发一个framework,提供给公司内部不同的产品线使用. 之间遇到的一个问题,就是STL容器的使用, 而结论是不要在公共接口中传递STL容器: 这里说的STL容器,但主要则是指容 ...

  5. STL容器的内存分配

    这篇文章参考的是侯捷的<STL源码剖析>,所以主要介绍的是SGI STL实现版本,这个版本也是g++自带的版本,另外有J.Plauger实现版本对应的是cl自带的版本,他们都是基于HP实现 ...

  6. 转:STL容器里存放对象还是指针

    一.问题的引出: 容器可以存放对象,可以存放指针,这里要谈的是两者的使用问题.就是什么时候存放对象更好,什么时候存放指针更好? 二.问题的分析过程: 1. 首先说下stl容器的工作方式   对于内建类 ...

  7. STL容器底层数据结构的实现

    C++ STL 的实现: 1.vector      底层数据结构为数组 ,支持快速随机访问   2.list            底层数据结构为双向链表,支持快速增删   3.deque     ...

  8. STL容器之一vector

    STL中最简单也是最有用的容器之一是vector<T>类模板,称为向量容器,是序列类型容器中的一种. 1.vector<T> 对象的基本用法(1)声明:vector<ty ...

  9. STL容器与拷贝构造函数

    所有容器提供的都是“value语意”而非“reference语意”.容器内进行元素的安插操作时,内部实施的是拷贝操作,置于容器内.因此STL容器 的每一个元素都必须能够拷贝.---<<C+ ...

  10. STL 容器的概念

    STL 容器的概念 在实际的开发过程中,数据结构本身的重要性不会逊于操作于数据结构的算法的重要性,当程序中存在着对时间要求很高的部分时,数据结构的选择就显得更加重要. 经典的数据结构数量有限,但是我们 ...

随机推荐

  1. “古老”编程语言的最新选择!华为云发布CodeArts IDE for C/C++

    摘要:华为云CodeArts IDE for C/C++正式上线,欢迎体验. 本文分享自华为云社区<"古老"编程语言的最新选择!华为云发布CodeArts IDE for C ...

  2. JS引擎中的线程,事件循环,上下文

      线程 浏览器中有哪些进程呢? 1.浏览器进程:浏览器的主进程,负责浏览器的界面界面显示,与用户交互,网址栏输入.前进.后退,以及页面的创建和销毁. 2.渲染进程(浏览器内核):默认一个tab页面一 ...

  3. 常量接口 vs 常量类 vs 枚举区别

    把常量定义在接口里与类里都能通过编译,那2者到底有什么区别呢? 那个更合理? 常量接口 public interface ConstInterfaceA { public static final S ...

  4. HiveSQL在使用聚合类函数的时候性能分析和优化详解

    概述 前文我们写过简单SQL的性能分析和解读,简单SQL被归类为select-from-where型SQL语句,其主要特点是只有map阶段的数据处理,相当于直接从hive中取数出来,不需要经过行变化. ...

  5. Shodan使用指南

    Shodan是用于搜索连接到互联网的设备的工具.与搜索引擎可以帮助你找到网站不同,Shodan可以帮助你找到有关台式机,服务器,IoT设备等的信息.此信息包括元数据,例如在每个设备上运行的软件. Sh ...

  6. 一份保姆级的Stable Diffusion部署教程,开启你的炼丹之路

    市面上有很多可以被用于AI绘画的应用,例如DALL-E.Midjourney.NovelAI等,他们的大部分都依托云端服务器运行,一部分还需要支付会员费用来购买更多出图的额度.在2022年8月,一款叫 ...

  7. .NET Core 3.1使用docker打包并部署

    目录 简介 环境介绍 开发环境 部署环境 编写Dockerfile文件 生成Docker镜像 运行容器 访问接口 结语 简介 本文主要说明使用.NET Core 3.1搭建的站点如何使用docker打 ...

  8. PDF书签的编辑器,基于(python、Tkinter)

    使用 脚本 在github下载源码. 安装python3 安装必要的python包 pip install numpy pip install pandas pip install PyMuPDF p ...

  9. typedef和define有什么区别

    typedef 和define 都是替一个对象取一个别名,以此增强程序的可读性,区别如下: 使用不用 define 定义后面不用加分号,并且它的别名在对象的前面 typedef需要加分号,并且它的别名 ...

  10. Blazor中用浏览器打开一个链接的最好方法

    适用于Blazor Wasm和Blazor SSR 调用下面的js方法 说一下为什么不用window.open,有可能被拦截是小问题,大问题是打开新页面未加载完时,回到原页面,大概率卡死,无法点击任何 ...