目录
1.概述
在之前,我们讲了迭代器设计模式,分析了它的结构、角色以及优缺点:
在 STL 中,算法通常作为函数模板来实现,而数据结构(容器)则是模板类。这种分离使得算法可以独立于特定的数据结构来编写和使用,只要数据结构满足算法所需的接口(例如迭代器接口)。
STL的中心思想是将算法与数据结构分离,彼此独立设计,最后在用iterator将他们结合在一起,获得最大的适配性。通过"迭代器"接口,已经实现了将算法与数据结构分离的目的。
2.容器类
容器类是STL非常重要的一部分,它们提供了用于存储、检索和操作数据对象(如整数、浮点数、字符串等)的模板类。以下是STL中一些主要的容器类:
2.1.序列容器
这些容器中的元素可以看作是线性排列的。
std::vector: 动态数组,可以动态地增加和减少元素。
std::deque: 双端队列,支持在序列的开头和结尾处快速插入和删除元素。
std::list: 双向链表,允许在任何位置进行快速插入和删除操作。
std::forward_list: 单向链表,仅支持向前迭代,并且在序列的开头进行快速插入和删除操作。
std::array: 固定大小的数组,不是动态的,但提供了更好的性能。
std::string: 特殊的动态字符数组,用于存储字符串。
std::stack: 后入先出(LIFO)的容器适配器,通常基于std::deque或std::vector实现。
std::queue: 先入先出(FIFO)的容器适配器,通常基于std::deque或std::list实现。
std::initializer_list: 初始化其它容器的容器,只是std::initializer_list不存储数据,详见:
2.2.关联容器
这些容器中的元素是按键(key)存储的,并且按键进行排序。
std::set: 集合,包含唯一的元素,按键排序。
**std::multiset:**允许包含重复元素的集合。
std::map: 关联数组,其中每个元素都是一对键/值对,按键排序。
std::multimap: 允许键重复的关联数组。
std::unordered_set: 无序集合,包含唯一的元素,但不按键排序。
std::unordered_multiset: 允许包含重复元素的无序集合。
std::unordered_map: 无序关联数组,其中每个元素都是一对键/值对,但不按键排序。
std::unordered_multimap: 允许键重复的无序关联数组。
2.3.容器适配器
这些类不是容器本身,但它们为其他容器类提供了不同的接口。
std::stack: 后入先出(LIFO)的容器适配器。
std::queue: 先入先出(FIFO)的容器适配器。
**std::priority_queue:**优先级队列,其中元素按优先级排序(通常基于堆实现)。
2.4.数组
对于C数组,指针扮演迭代器的角色。
cpp
int myInts[100];
std::for_each(myInts, myInts + 100, doSomething);
严格来说,myInts不是指针而是数组,但它仍然提供对数组的第一个元素的访问,而myInts + 100指向"结束后"的地址,符合begin-end的语义。
因此,C数组可以与算法一起使用,在旧代码中非常有帮助。
需要注意的是,自C++11以来引入了一种新的统一语法,使用std::begin(和std::end)自由函数(而不是类方法)。它们可以统一地用于任何具有可以无参数调用的begin(或end)方法的类型,并且也可以用于C数组。
下面的代码示例说明了这种统一性:
cpp
int myInts[100];
std::vector<int> vec(100, 0); // 大小为100且初始化为0
std::for_each(std::begin(vec), std::end(vec), doSomething);
std::for_each(std::begin(myInts), std::end(myInts), doSomething);
这使得使用C数组变得更加简单,并且对于通用代码非常方便。
需要注意的是,对于C数组,必须显式地写出std
命名空间,因为它无法使用ADL,但对于vector
可以省略std
命名空间。
3.迭代器
- 上述序列容器 和关联容器都提供了标准的迭代器操作:
正向迭代器:begin()和end()
反向迭代器:rbegin()和rend()
常量正向迭代器:cbegin()和cend()(或者const容器上的begin()和end())
常量反向迭代器:crbegin()和crend()(或者const容器上的rbegin()和rend())
-
容器迭代器是不提供这些操作的。
-
数组可以用std::begin()和std::end()来获取正向迭代器、反向迭代器、常量正向迭代器和常量反向迭代器。
4.重用标准迭代器
如果集合确实需要领域功能,或者只想要标准容器提供的部分功能,可能需要定义一个包装标准容器的类。在这种情况下,可以使用标准容器的迭代器来实现迭代器:
cpp
// 接口
class FlowCollection
{
public:
// ...领域接口...
// 允许访问数据的迭代器
using const_iterator = std::vector<Flow>::const_iterator;
const_iterator begin() const;
const_iterator end() const;
// 允许修改数据的迭代器
using iterator = std::vector<Flow>::iterator;
iterator begin();
iterator end();
// 其他迭代器...
private:
std::vector<Flow> m_flows;
// ...领域数据...
};
// 实现
FlowCollection::iterator FlowCollection::begin()
{
return m_flows.begin();
}
那么算法就可以这样调用,以排序为例:
cpp
FlowCollection data;
std::sort(data.begin(), data.end());
5.总结
STL 中的算法是一组函数模板,它们接受迭代器作为参数,并对迭代器范围内的元素执行操作。这些算法包括排序、搜索、复制、删除等操作。由于算法是独立于数据结构的,因此它们可以与任何支持迭代器接口的容器一起使用。
通过将算法与容器分离,STL 允许程序员根据需要组合不同的算法和容器。例如,你可以使用排序算法对向量进行排序,或者使用搜索算法在集合中查找元素。这种灵活性使得 STL 能够适应各种不同的应用场景。
总的来说,STL 的设计方式通过将算法与数据结构分离,实现了代码的可重用性、可维护性和灵活性。这使得 STL 成为 C++ 程序员在处理数据结构和算法时的重要工具。