文章目录
第一讲 从体系结构角度讲解STL
1. C++ Standard Library vs Standard Template Library
- C++中的STL实现了数据结构和算法的分离,被包括在C++标准库(C++ Standard Library)中;
- C++ Standard Library > STL(六大部件);
2. STL体系结构基础介绍
1. STL六大组件
- 容器(Container):一些封装的数据结构,例如list、deques、vector、front list等;
- 迭代器(Iterator):为访问容器中的数据而写的一些方法;
- 算法(Algorithm):为处理容器中的数据而写的一些方法;
- 仿函数(Functor):实现了函数功能的类。(本质是一个类,里面只实现算法(函数)功能,调用时看着像使用函数,但使用的是类创建对象里面的方法);
- 适配器(Adaptor):类和类之间转换、合作时,由于代码不兼容,因此需要另一些类代码完成中间转换、合作的“手续”事宜。这些因为克服STL中类代码合作、转换的代码封装称为适配器;
- 分配器(Allocator):给数据分配内存一些代码。
2. STL组件关系
- STL把数据结构和算法划分开,这不符合**面向对象OO(Object-Oriented)**编程思路;
- 容器(Container) == 数据结构;
- 为用户隐藏了内存的相关内容;
- 分配器(Allocator)负责内存的相关工作;
- 迭代器(Iterator)作为容器和算法之间联系的桥梁,是泛型类型的指针;
3. 组件使用示例
- 14行的not1和bind2nd是函数的adapter适配器;
- 13行的代码表示:找到容器中 >= 40 的元素个数;
4. 常见时间复杂度
5. STL所有容器头尾的指向遵循左闭右开原则
- 尾部迭代器指向容器最后一个元素的下一个位置(不属于容器);
6. 遍历容器方法
- 常规索引遍历for;
- 迭代器遍历;
- 基于范围的for循环(range-based for statement);
3. 容器结构与分类
1. Containers分类
-
Sequence Containers(序列式容器):
-
Array(数组,C++11):
- 大小固定,连续空间;不能进行增加和删除;
- 理解为内置数组的升级版;
-
Vector:
- 连续空间,可以在尾部扩展【push_back(…)(在头部扩展耗费时间,需要全部数据移动)】;
- 每次空间不足时,都会把可用空间扩充两倍;容易造成空间浪费;
- 扩容过程:
- 找到另一块连续的空间,将原来的数据转移过去,过程缓慢;
-
Deque(双端队列):
- (让用户以为整体是)连续空间,可以在两端扩展【push_front(…)和push_back(…)】;
- Deque其实是分段连续(并不是整体连续),如图;
- Deque维护一个连续指针空间map,每个指针指向一段连续的等量空间buffer;
- 【但Deque会制造假象,让用户误以为是整体连续的;】
- 扩容过程:
- 每次扩充一个buffer;
- 当push_back(…)空间不足时,在指针数组尾部新增一个指针,指向新的buffer;
- 当push_front(…)空间不足时,在指针数组头部新增一个指针,指向新的buffer;
-
List(双向链表):
- 离散空间,可以在两端扩展【push_front(…)和push_back(…)】;
- 扩容过程:
- 每次扩充一个;
-
Forward-List(单向链表,C++11):
- 离散空间,在头部扩展【push_front(…)(在尾部扩展太耗费时间)】;
- 扩容过程:
- 每次扩充一个;
- 每次扩充一个;
-
-
Associative Containers(关联式容器):
-
key有序:
- Set / Multiset【理解成key就是value,value就是key】:
- Set(key不能重复);
- Multiset(key可以有重复);
- Map / Multimap:
- Map(key不能重复);
- Multimap(key可以有重复);
- 底层实现结构都是红黑树;
- Set / Multiset【理解成key就是value,value就是key】:
-
key无序(C++11):
- Unordered Set / Multiset:
- Unordered Map / Multimap:
- 底层实现结构都是 Separate Chaining(拉链法)的HashTable;
-
-
扩容过程:
+ 当元素的个数 >= bucket(篮子)的个数:
+ 开始以8倍进行扩容;
+ 当bucket数量 >= 512,每次扩容两倍;
+ 每次扩容所有元素需要重新计算;
2. 特殊的容器stack、queue
- stack、queue其实不是容器,而是容器适配器;
- 它们的缺省底层结构是Deque;
- 因为stack、queue有特殊的结构性质,所以stack、queue没有迭代器:
- 如果有提供迭代器,用户可能使用迭代器,进行修改数据、删除数据,这会破坏stack、queue的特殊结构;
3. Sequence Containers的使用
-
使用插入、排序、查找、查看首尾;
-
clock()函数:返回程序开始到执行这个函数的时间,单位毫秒;
-
array的简单使用
- 不能插入、删除;
- 赋值:c[i] = value;
- 属性:【以下函数后面重复不再说明】
- size()函数:获取目前存在的元素数;
- 获取首尾:front()、back();
- data()函数:返回容器的起始地址;
-
vector的简单使用
- 插入:push_back(…);
- 属性:
- capacity()函数:返回vector能存储数据的个数;
- 【每次空间不足时,进行两倍扩展】
-
list的简单使用
- 插入:push_back(…)和push_front(…);
- 属性:
- max_size()函数:返回允许的最大存储数据的个数【后面不再重复说明】;
- 排序:
- 注意197行,有自定义的sort(…);
- 有一些容器会自定义sort(…),优先使用容器自定义的sort(…)函数;
-
forward_list简单使用
- 插入:
- push_front(…);
- Forward-List没有push_back(…)函数,这样插入速度太慢,只有push_front(…),和vector没有push_front(…)函数的道理一样;
- 属性:没有back()函数;
- 排序:有自定义sort(…)函数;
- 插入:
-
deque简单使用
- 插入:push_back(…)和push_front(…);
- 插入:push_back(…)和push_front(…);
4. Associative Containers的使用
-
mulitset的简单使用:
- 插入:insert(…);
- 删除:erase(…);
- 查找:有自定义的find(…)函数;
-
mulitmap的简单使用:
- 插入:insert(pair<…, …>(…, …));
- 删除:erase(…);
- 查找:有自定义的find(…)函数;
-
unordered_multiset的简单使用:
- 插入:insert(…);
- 删除:erase(…);
- 查找:有自定义的find(…)函数;
- 属性:
- bucket_count():返回篮子的个数;
- bucket的数量一定 > 元素的数量;
- load_factor():计算载重因子;
- max_bucket_count():最大的篮子个数;
- max_load_factor():最大的载重因子;
- bucket_count():返回篮子的个数;
-
unordered_multimap的简单使用:
- 插入:insert(pair<…, …>(…, …));
- 删除:erase(…);
- 查找:有自定义的find(…)函数;
-
【key值不能重复】的容器使用:
- 插入、删除、查找操作和multi的差不多;区别在于key值不能重复;
- set;
- map;
- unordered_set;
- unordered_map;
4. allocator分配器
1. STL容器的默认分配器
- STL容器的默认分配器 == std::allocator;
- 容器的构造函数:
2. 其他分配器
- 其他分配器都是非标准库的内容,是额外扩充的,从include的头文件可以看出(不是标准库的形式);
- 其他分配器的头文件、namespace以及使用如下图所示;
- 以list为例,在构造函数使用不同的分配器;
- 以list为例,在构造函数使用不同的分配器;
3. 直接使用分配器
- 分配器作为一个class,【虽然可以直接使用它,但没有这个必要,建议直接使用容器】;
- 下图简单示范如何直接使用分配器:
- 分配和释放都需要指定大小;
- 分配和释放都需要指定大小;