STL的本质
通过前面的学习以及使用,我们对STL已经有了一定的认识。通俗说:STL是Standard Template Library(标准模板库),是高效的C++程序库,其采用泛型编程思想对常见数据结构(顺序表,链表,栈和队列,堆,二叉树,哈希)和算法(查找、排序、集合、数值运算…)等进行封装,里面处处体现着泛型编程程序设计思想以及设计模式,已被集成到C++标准程序库中。具体说:STL中包含了容器、适配器、算法、迭代器、仿函数以及空间配置器。 STL设计理念:追求代码高复用性以及运行速度的高效率,在实现时使用了许多技术,因此熟悉STL不仅对我们正常使用有很大帮助,而且对自己的知识也有一定的提高。
STL的六大组件
容器
容器,置物之所也。STL中的容器,可以划分为两大类:序列式容器和关联式容器。
算法
算法:问题的求解步骤,以有限的步骤,解决数学或逻辑中的问题。 STL中的算法主要分为两大类:与数据结构相关算法(容器中的成员函数)和通用算法(与数据结构不相干)。STL中通用算法总共有70多个,主要包含: 排序,查找,排列组合,数据移动,拷贝,删除,比较组合,运算等。
C++标准函数库中,为我们提供了一个algorithm库,里面包含了十分多的常用算法,以下只列出了部分常用的算法:
算法名称 | 算法功能 |
---|---|
accumulate | 元素统计 |
binary_search | 二分查找 |
copy | 拷贝 |
copy_backward | 逆向拷贝 |
copy_n | 拷贝n个元素 |
count | 计数 |
count_if | 在特定条件下计数 |
equal | 判断两个区间相等与否 |
fill | 填充元素 |
fill_n | 填充元素n次 |
find | 循环查找 |
find_if | 循环查找符合特定条件元素 |
find_end | 查找某个子序列的最后一次出现点 |
find_first_of | 查找某个元素首次出现点 |
for_each | 对区间内的每隔一元素实行某种操作 |
is_heap | 判断某区间是否为一个heap |
is_sorted | 判断某区间是否已排序 |
lexicographical_compare | 以字典顺序进行比较 |
max | 获取最大值 |
max_element | 最大值所在位置 |
merge | 合并两个序列 |
min | 获取最小值 |
min_element | 最小值所在位置 |
next_permutation | 获取下一个排列组合 |
pre_permutation | 获取前一个排列组合 |
partial_sort | 局部排序 |
partial_sum | 局部求和 |
partition | 分割 |
remove | 删除某类元素 |
remove_copy | 删除某类元素并将结果拷贝到另一个容器中 |
remove_if | 有条件的删除某类元素 |
replace | 替换某类元素 |
replace_if | 有条件的替换 |
reverse | 反转序列 |
sort | 排序(不稳定) |
stable_partition | 分割并保持元素的相对次序 |
stable_sort | 分割并保持相等元素的相对位置(稳定排序算法) |
unique | 取出重复性元素 |
make_heap | 创建堆 |
push_heap | 堆插入 |
pop_heap | 堆删除 |
sort_heap | 堆排序 |
迭代器
什么是迭代器
迭代器是一种设计模式,让用户通过特定的接口访问容器的数据,不需要了解容器内部的底层数据结构。 C++中迭代器本质:是一个指针,让该指针按照具体的结构去操作容器中的数据。
为什么需要迭代器
通过前面算法的学习了解到:STL中算法分为容器相关联与通用算法。所谓通用算法,即与具体的数据结构无关,比如:
template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
for ( ;first!=last; first++){
if ( *first==value )
break;
}
return first;
}
find算法在查找时,与具体的数据结构无关,只要给出待查找数据集合的范围,find就可在该范围中查找,找到返回该元素在区间中的位置,否则返回end。
问题:对于vector、list、deque、map、unordered_set等容器,其底层数据结构均不相同,那find算法是怎么统一向后遍历呢?
vector的底层结构:
list的底层结构:
map的底层结构:
迭代器应该由谁负责提供
每个容器的底层结构都不同,为了降低算法使用容器时的复杂度,底层结构应该对于算法透明,迭代器就充当了算法与容器之间的转接层,因此:每个容器的迭代器应该由容器设计者负责提供,然后容器按照约定给出统一的接口即可。
比如:
// vector中:
typedef T* iterator;
iterator begin();
iterator end();
find(v.beign(), v.end(), 5);
// list中
typedef list_iterator<T, T&, T*> iterator;
iterator begin();
iterator end();
find(l.begin(), l.end(), 5);
迭代器实现原理
容器底层结构不同,导致其实现原理不同,容器迭代器的设计,必须结合具体容器的底层数据结构。 比如:
- 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
代器与类的融合
-
定义迭代器类
-
在容器类中统一迭代器名字
// 比如list:
template <class T, class Alloc = alloc>
class list
{
// ...
typedef __list_iterator<T, T&, T*> iterator;
// ...
};
- 在容器类中添加获取迭代器范围的接口:
template <class T, class Alloc = alloc>
class list
{
// ...
iterator begin(){ return (link_type)((*node).next);}
iterator end(){ return node;}
// ...
};
反向迭代器
反向迭代器:正向迭代器的适配器,即正向迭代器++往end方向移动,–往begin方向移动,而反向迭代器++则往begin方向移动,–则向end方向移动。
适配器
适配器:又接着配接器,是一种设计模式,简单的说:需要的东西就在眼前,但是由于接口不对而无法使用,需要对其接口进行转化以方便使用。即:将一个类的接口转换成用户希望的另一个类的接口,使原本接口不兼容的类可以一起工作。
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());
for (auto e : v)
cout << e << " ";
cout << endl;
// 删除容器中3的倍数
auto pos = remove_if(v.begin(), v.end(), Mod3());
v.erase(pos, v.end());
// 将容器中的元素打印出来
// 注意:对于功能简单的操作,可以使用C++11提供的lambda表达式来代替
// lambda表达式实现简单,其在底层与仿函数的原理相同,编译器会将lambda表达式转换为仿函数
for_each(v.begin(), v.end(), [](int data){cout << data << " "; });
cout << endl;
return 0;
}