C++ STL新手手册

450 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


什么是STL?

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

通俗说:STL是高效的C++程序库,其采用泛型编程思想对常见数据结构(顺序表,链表,栈和队列,堆,二叉树,哈希) 和算法(查找、排序、集合、数值运算...)等进行封装,里面处处体现着泛型编程程序设计思想以及设计模式,已被集成到C++标准程序库中。

STL设计理念:追求代码高复用性及运行速度的高效率,在实现时使用了许多技术。

STL版本

  1. HP版本(原始版本): Alexander StepanovMeng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本:所有STL实现版本的始祖。

  2. P. J. 版本 由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  3. RW版本 由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  4. SGI版本 由Silicon Graphics Computer SystemsInc公司开发,继承自HP版本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。


STL六大组件

  1. 容器:string、vector、list、deque、map、set、mutimap等。
  2. 算法:find、swap、reverse、sort、merge等。
  3. 迭代器:iterator、const_iterator、reverse_iterator、const_reverse_iterator等。
  4. 仿函数:greater、less等。
  5. 配接器:stack、queue、priority_queue等。
  6. 空间配置器:allocator。

容器

容器,置物之所也。 STL中的容器,可以划分为两大类:序列式容器和关联式容器。 关联式容器底层分为:树形结构与哈希结构。

容器的使用与接口要熟悉一下~

算法

算法:问题的求解步骤,以有限的步骤,解决数学或逻辑中的问题。 STL中的算法主要分为两大类:

  1. 与数据结构相关算法(容器中的成员函数)。
  2. 通用算法(与数据结构不相关)。

STL中通用算法总共有70多个,主要包含:排序,查找,排列组合,数据移动,拷贝,删除,比较组合,运算等。

迭代器

迭代器是一种设计模式,让用户通过特定的接口访问容器的数据,不需要了解容器内部的底层数据结构,同时提供了使用同一方式访问各个结点

C++中迭代器本质:是一个指针,让该指针按照具体的结构去操作容器中的数据。

迭代器原理: 容器底层结构不同,导致其实现原理不同,容器迭代器的设计必须结合具体容器的底层数据结构。如:

  1. vector 因为vector底层结构为一段连续空间,迭代器前后移动时比较容易实现,因此vector的迭代器实际是对原生指针的封装,即:typedef T* iterator

  2. list list底层结构为带头结点的双向循环链表,迭代器在移动时,只能按照链表的结构前后依次移动,因此链表的迭代器需要对原生指针进行封装,因为当对迭代器++时,应该通过节点中的next指针域找到下一个节点。

如果迭代器不能直接使用原生态指针操作底层数据时,必须要对指针进行封装,在封装时需要提供以下方法:

  1. 迭代器能够像指针一样方式进行使用:重载pointer operator*() / reference operator->()

  2. 能够让迭代器移动

向后移动:self& operator++() / self operator++(int)

向前移动:self& operator--() / self operator--(int) (注意:有些容器不能向前移动,比如forward_list)

  1. 支持比较,因为在遍历时需要知道是否移动到区间的末尾 bool operator!=(const self& it)const / bool operator==(const self& it)const

适配器

适配器:又称配接器,是一种设计模式。简单的说:需要的东西就在眼前,但是由于接口不对而无法使用,需要对其接口进行转化以方便使用。 即:将一个类的接口转换成用户希望的另一个类的接口,使原本接口不兼容的类可以一起工作。

STL中适配器总共有三种类型:

  • 容器适配器:stackqueue stack的特性是后进先出,queue的特性为先进先出,该种特性deque的接口完全满足,因此stackqueue在底层将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的缺陷

  1. STL库的更新太慢。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
  2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
  3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
  4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。

小结

在工作实战中,STL等使用十分频繁。网上有句话说:“不懂STL,不要说你会C++”。 STLC++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新编码实现,站在前人的肩膀上,健步如飞的快速开发~