本文已参与「新人创作礼」活动,一起开启掘金创作之路。
什么是STL?
STL(standard template libaray
-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
通俗说:STL
是高效的C++
程序库,其采用泛型编程思想对常见数据结构(顺序表,链表,栈和队列,堆,二叉树,哈希) 和算法(查找、排序、集合、数值运算...)等进行封装,里面处处体现着泛型编程程序设计思想以及设计模式,已被集成到C++
标准程序库中。
STL
设计理念:追求代码高复用性以及运行速度的高效率,在实现时使用了许多技术。
STL版本
-
HP
版本(原始版本):Alexander Stepanov
、Meng Lee
在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。HP
版本:所有STL
实现版本的始祖。 -
P. J.
版本 由P. J. Plauger
开发,继承自HP
版本,被Windows Visual C++
采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。 -
RW
版本 由Rouge Wage
公司开发,继承自HP
版本,被C+ + Builder
采用,不能公开或修改,可读性一般。 -
SGI
版本 由Silicon Graphics Computer Systems
,Inc
公司开发,继承自HP
版本。被GCC(Linux)
采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。
STL六大组件
- 容器:string、vector、list、deque、map、set、mutimap等。
- 算法:find、swap、reverse、sort、merge等。
- 迭代器:iterator、const_iterator、reverse_iterator、const_reverse_iterator等。
- 仿函数:greater、less等。
- 配接器:stack、queue、priority_queue等。
- 空间配置器:allocator。
容器
容器,置物之所也。
STL
中的容器,可以划分为两大类:序列式容器和关联式容器。
关联式容器底层分为:树形结构与哈希结构。
容器的使用与接口要熟悉一下~
算法
算法:问题的求解步骤,以有限的步骤,解决数学或逻辑中的问题。
STL
中的算法主要分为两大类:
- 与数据结构相关算法(容器中的成员函数)。
- 通用算法(与数据结构不相关)。
STL
中通用算法总共有70
多个,主要包含:排序,查找,排列组合,数据移动,拷贝,删除,比较组合,运算等。
迭代器
迭代器是一种设计模式,让用户通过特定的接口访问容器的数据,不需要了解容器内部的底层数据结构,同时提供了使用同一方式访问各个结点。
C++
中迭代器本质:是一个指针,让该指针按照具体的结构去操作容器中的数据。
迭代器原理: 容器底层结构不同,导致其实现原理不同,容器迭代器的设计必须结合具体容器的底层数据结构。如:
-
vector
因为vector
底层结构为一段连续空间,迭代器前后移动时比较容易实现,因此vector的迭代器实际是对原生指针的封装,即:typedef T* iterator
-
list
list
底层结构为带头结点的双向循环链表,迭代器在移动时,只能按照链表的结构前后依次移动,因此链表的迭代器需要对原生指针进行封装,因为当对迭代器++
时,应该通过节点中的next
指针域找到下一个节点。
如果迭代器不能直接使用原生态指针操作底层数据时,必须要对指针进行封装,在封装时需要提供以下方法:
-
迭代器能够像指针一样方式进行使用:重载
pointer operator*() / reference operator->()
-
能够让迭代器移动
向后移动:self& operator++() / self operator++(int)
向前移动:self& operator--() / self operator--(int)
(注意:有些容器不能向前移动,比如forward_list
)
- 支持比较,因为在遍历时需要知道是否移动到区间的末尾
bool operator!=(const self& it)const / bool operator==(const self& it)const
适配器
适配器:又称配接器,是一种设计模式。简单的说:需要的东西就在眼前,但是由于接口不对而无法使用,需要对其接口进行转化以方便使用。 即:将一个类的接口转换成用户希望的另一个类的接口,使原本接口不兼容的类可以一起工作。
STL中适配器总共有三种类型:
- 容器适配器:
stack
和queue
stack
的特性是后进先出,queue
的特性为先进先出,该种特性deque
的接口完全满足,因此stack
和queue
在底层将deque
容器中的接口进行了封装。
template < class T, class Container = deque<T> >
class stack { ... };
template < class T, class Container = deque<T> >
class queue { ... };
- 迭代器适配器:反向迭代器
反向迭代器
++
和--
操作刚好和正向迭代器相反,因此:反向迭代器只需将正向迭代器进行重新封装即可。 - 函数适配器
仿函数
仿函数:一种具有函数特征的对象,调用者可以像函数一样使用该对象 ,为了能够“行为类似函数”,该对象所在类必须自定义函数调用运算符operator()
,重载该运算符后,就可在仿函数对象的后面加上一对小括号()
,以此调用仿函数所定义operator()
操作,所以仿函数也成为函数对象。
仿函数一般配合算法,作用就是:提高算法的灵活性。
#include <vector>
#include <algorithm>
class Mul2{
public:
void operator()(int& data){
data <<= 1;
}
};
class Mod3{
public:
bool operator()(int data){
return 0 == data % 3;}
};
int main(){
// 给容器中每个元素乘2
vector<int> v{1,2,3,4,5,6,7,8,9,0};
for_each(v.begin(), v.end(), Mul2());
// 删除容器中3的倍数
auto pos = remove_if(v.begin(), v.end(), Mod3());
v.erase(pos, v.end());
return 0;
}
空间配置器
对内存进行二级或三级管理,避免产生过多内存碎片。
STL的缺陷
STL
库的更新太慢。这个得严重吐槽,上一版靠谱是C++98
,中间的C++03
基本一些修订。C++11出来已经相隔了13
年,STL
才进一步更新。STL
现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。STL
极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。STL
的使用会有代码膨胀的问题,比如使用vector/vector/vector
这样会生成多份代码,当然这是模板语法本身导致的。
小结
在工作实战中,STL等使用十分频繁。网上有句话说:“不懂STL
,不要说你会C++
”。
STL
是C++
中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新编码实现,站在前人的肩膀上,健步如飞的快速开发~