C++ | STL I
终于他喵的写到STL了淦, 前置的乱七八糟的东西太多了, 没错我说的就是你, 友元!
我打算先从STL的用法开始, 学习到STL的底层, STL系列也会写的更为详细, 因为他真的, 很重要
前言
STL(Standard Template Library) 源自于C艹第一个标准化版本C艹98(不是CC98哦), 它提供了强大的容器库(Containers library) 和算法库(Algorithm library) 以供使用者直接调用, 其背后体现了泛型编程(Generic programming) 的思想; 即使你像我一样对上面的一堆名词不甚了解, 也可以很好的利用它所提供的常见的数据结构和算法解决很多问题.
参考资料
- 咸鱼暄的代码空间 | 模板 (I) - 基本知识与 STL 使用
- 咸鱼暄的代码空间 | 模板 (II) - 理解 STL:迭代器与函数对象
- 咸鱼暄的代码空间 | 理解 STL - 迭代器与函数对象
- YouTube | Back to Basics: Classic STL - Bob Steagall - CppCon 2021
为什么要STL
在我数据结构杂谈(一)这篇文章中, 我曾经提到, ADT的出现, 是为了处理更加普遍的情况, 而算法正是基于ADT实现的. 在这里, 我想用更加学术的语言重制这句话.
解释数据结构与算法, 就要从数据这个概念本身开始, 在那一片文章中, 我曾经将胡图图一家人看作是一个数据结构, 把胡图图, 张小丽等人看作是数据, 如果我们再深挖下去, 思考一个人是否还有其他从属于他自己的属性. 答案显然是肯定的. 胡图图是张小丽的儿子, 这个关系就可以视作一个元素, 同样, 胡图图的生理数据, 比如他的三根头发, 也是一个元素. 因此我们可以说:
-
数据几乎总是 元素 的 集合 | Data is almost always collections of elements.
-
每种集合有一定的 表现形式 | Each collection of elements has some representation.
而针对特定表现形式的数据结构, 也就是集合的表现形式的特定操作, 就是算法:
- 有很多处理方法(算法) | There are many kinds of progressing(algorithms)
然而在我们的宇宙中, 即便是一个问题空间中, 数据结构的数量总是一个非常巨大的数字N, 假如数据结构两两之间需要进行数据交换, 我们就要设计C(N,2)种算法, 这个数字不是我们能轻易接受的.
为了解决以上问题, C艹引入了泛型编程的概念, 可以通俗的理解为模板, 来解决程序段复用性的问题, 而STL正是其中一个最具典型性的代表–C标准委员会提供了一组库, 以供使用者复用这个库里面的代码.
STL库最初的设计准则, 即是:
- 综合性 Comprehensive
- 可扩展性 Extensible
- 高效性 Efficient
- 自然性 Natural
正是由于对上述准则的坚持, C艹STL库才能至今仍在跟进维护 , 不像某个友元的概念
容器、算法与迭代器
在STL里面, 我们用容器container来描述一个数据结构, 或者说是元素集合的储存, 容器为下列操作提供支持:
- 增删元素
- 通过相关的迭代器读取或更新某个元素
- 容器的迭代器脱离于容器的内部结构而存在,但是能完成对容器的特定操作并支持算法的实现
算法algorithm对应的就是普遍意义上的算法, 是对容器的操作, 但是算法并不能直接操作容器, 而是通过定义清晰的迭代器
迭代器iterator为容器与算法的交流提供一个共同单位, 确保有一个提供清晰的方法得到元素
容器
STL为我们提供了丰富的容器库Container library,其中主要包含了三大类,十数种容器模板:
-
序列容器Sequence container
序列容器是抽象于数据按某一个顺序排列的数据结构的容器:- 向量vector:虽然单词原意是向量,但是人们更愿意称之为可变数组 ,这是因为相比于数组,vector可以通过成员函数动态的改变自己的长度
- 双向队列deque:一个可以在两端进行增删操作的队列,假如对其两端的操作权限作出限制,可以特化为队列或栈,因此deque相比队列或是栈,更加的灵活,但是一定程度上,也更加臃肿
- 数组array:固定长度的数组
- 双向链表list:相当于迭代器没有随机访问能力的deque
- 单向链表forward_list:单向的链表
-
关联容器Associative container
关联容器是抽象于数据没有一个固定排列,而是通过相互之间的关系组织在一起的数据结构的容器,在C艹(以及大多数其他高级语言中)中是用红黑树(一种高效的BST)实现的:- 集合set:储存的是无重复、无顺序的数据集合,与数学上集合的概念相仿,但不能储存无穷多的元素(显然)
- 映射map:在有些高级语言中,map也被叫做dictionary,map的储存单位是一一对应的键值对,其中键是无序、无重的
- 可重集合multiset
- 可重映射multimap:上述容器的可重版本,允许元素的重复
-
无序关联容器Unordered associative container
无序关联容器是上述关联容器的无序版,其底层使用hash表而不是红黑树实现:- unordered_set
- unordered_map
- unordered_multiset
- unordered_multimap
-
适配器Adaptor
适配器不是严格意义上的容器,而是对既有容器(包括STL容器和使用者自定义的容器)接口的封装:- 栈stack
- 队列queue
- 优先队列priority_queue
扩展阅读:
C艹STL库官方文档 | 容器
算法
C++同样为我们提供了丰富的算法库Algorithm library,如同前面所说,算法是脱离于容器存在的,但是实现确是通过迭代器操作容器,因此在对算法做介绍之前,我们需要简单讲一下如何获取某个容器的迭代器。
auto i = v.begin(); |
这两个函数分别可以获取指向容器v开头或结尾的迭代器(是的,你可以将迭代器粗浅的理解为指针,一个有自己重载后的++运算符的特殊指针)
在上面的例子中,使用了一个全新的变量类型声明关键字auto,这代表了使用者并不关心i、j的具体变量类型,而是交由编译器自行推导(类似于模板函数参数表的推导)
不修改序列的操作
find()
auto res = find(start, stop, target); |
其中res、start、stop的数据类型均为迭代器,target的数据类型为v的元素的数据类型;
函数的作用是在v(通常是array或vector)中左闭右开区间[start,stop)上寻找值等于target的元素,并返回其迭代器,找不到则返回stop
count()
int cnt = count(start, stop, target); |
count_if()
int cnt = count_if(start, stop, p); |
其中p是一个谓词 ,可以是函数、函数指针、函数对象(即对()
进行重载)或lambda函数。
这个函数会返回区间上满足谓词p的元素的数量
for_each()
for_each(start, stop, func); |
类似于其他高级语言中的map()函数,即对区间上所有的元素调用函数func
修改序列的操作
reverse()
reverse(start, stop); |
fill()
fill(start, stop, value); |
排序操作
sort
sort(strat, end); //重载<运算符后 |
stable_sort
stable_sort(strat, end); //重载<运算符后 |
与sort()
不同的是, stable_sort()
是稳定的排序
partial_sort
partial_sort(start, middle, end); //重载<运算符后 |
重新排序, (默认)把最小的一部分元素按升序置于区间[start,middle)中
二分查找操作(对于有序序列)
binary_search
bool res = binary_search(start, stop, target, cmp); |
lower_bound
auto lower = lower_bound(start, stop, target, cmp); |
(默认)返回区间内大于target的第一个元素的迭代器
upper_bound
auto upper = upper_bound(start, stop, target, cmp); |
(默认)返回区间内不小于target的第一个元素的迭代器
其他操作
集合操作(对于有序序列)
includes(start1, stop1, strat2, stop2, cmp); //返回序列1是否包含了序列2 |
大小比较操作
auto maxn = max(a, b, cmp); |
堆操作
make_heap(start, stop, cmp); |
比较操作
bool res = equal(start1, stop1, start2); //判断两个对象是否相等 |
cmp函数参数
上文中所有带cmp
参数的函数, 都可以省略之, 编译器会自动用<
或重载后的<
来替代之.
在函数实现时:
x > y
等价于!(x < y)
或!cmp(x,y)
x != y
等价于!(x < y) && !(y < x)
或!cmp(x,y) && !cmp(y,x)
因此, 当你想按照降序排列某个序列时, 请使用
bool cmp(int x, int y){ |
扩展阅读: