STL实用技术专题
STL的从广义上讲分为三类: algorithm (算法)、container (容器)和iterator (迭代器),
STL详细的说六大组件
– 容器( Container )
– 算法( Algorithm )
– 迭代器( Iterator )
– 仿函数( Function object )
– 适配器( Adaptor)
– 空间配制器( allocator )
1. string
string 和char* 的比较:
string 是一个类, char* 是一个指向字符的指针。
string 封装了char* ,管理这个字符串,是一个char* 型的容器。
string 不用考虑内存释放和越界。
string 封装相关函数。
相关函数
const char *c_str() const; // 从string 取得const char* 的操作
string &operator+=(const string &s); // 把字符串s 连接到当前字符串结尾
string &operator+=(const char *s); // 把字符串s 连接到当前字符串结尾
int compare(const string &s) const; // 与字符串s 比较
int compare(const char *s) const; // 与字符串s 比较
string substr(int pos=0, int n=npos) const; // 返回由pos 开始的n 个字符组成的子字符串
find以及rfind函数。
replace函数
insert函数
erase函数
相关算法:
transform(s2.begin(), s2.end(), s2.begin(), toupper);
transform(s3.begin(), s3.end(), s3.begin(), tolower);
2. Vector
向量是表示可以改变大小的数组的序列容器。
就像数组一样,向量使用连续的存储位置作为元素,这意味着它们的元素也可以使用常量指向其元素的偏移来访问,并且与数组一样有效。但与数组不同,它们的大小可以动态变化,其存储由容器自动处理。
在内部,向量使用动态分配的数组来存储它们的元素。可能需要重新分配此数组,以便在插入新元素时增大大小,这意味着分配新数组并将所有元素移动到该数组。就处理时间而言,这是相对昂贵的任务,因此,每次将元素添加到容器时,向量都不会重新分配。
相反,矢量容器可以分配一些额外的存储空间以适应可能的增长,因此容器的实际容量可能大于包含其元素所需的存储容量(即,其大小)。库可以实现不同的增长策略以在内存使用和重新分配之间取得平衡,但无论如何,重新分配只应以对数增长的大小间隔发生,以便在向量末尾插入单个元素可以提供摊销的常量时间复杂性(见push_back)。
因此,与数组相比,向量消耗更多内存,以换取管理存储和以有效方式动态增长的能力。
与其他动态序列容器(deques,lists和forward_lists)相比,向量非常有效地访问其元素(就像数组一样)并且相对有效地从其末尾添加或删除元素。对于涉及在末尾以外的位置插入或删除元素的操作,它们的性能比其他位置差,并且与列表和forward_lists相比具有更少的一致的迭代器和引用。
相关函数
vector.size(); // 返回容器中元素的个数
vector.capacity() // 分配的内存大小
vector.empty(); // 判断容器是否为空
3. deque
双端队列
deque(通常发音为“deck”)是双端队列的不规则首字母缩写。双端队列是具有动态大小的序列容器,可以在两端(前端或后端)扩展或收缩。
特定库可以以不同方式实现deques,通常作为某种形式的动态数组。但无论如何,它们允许通过随机访问迭代器直接访问各个元素,并根据需要通过扩展和收缩容器来自动处理存储。
因此,它们提供类似于vector的功能,但是在序列的开头也有效地插入和删除元素,而不仅仅是在其结尾。但是,与向量不同,deques不能保证将其所有元素存储在连续的存储位置:通过将指针偏移到另一个元素来访问双端队列中的元素会导致未定义的行为。
vector和deque都提供了一个非常相似的接口,可以用于类似的目的,但内部都以完全不同的方式工作:虽然矢量使用需要偶尔重新分配以生长的单个数组,但是deque的元素可以分散在不同的存储块,容器在内部保存必要的信息,以便在恒定的时间内通过统一的顺序接口(通过迭代器)直接访问其任何元素。因此,deques在内部比矢量稍微复杂一些,但是这允许它们在某些情况下更有效地生长,特别是在非常长的序列中,其中重新分配变得更加昂贵。
对于涉及在开头或结尾以外的位置频繁插入或删除元素的操作,deques表现更差,并且与列表和转发列表相比具有更少的一致的迭代器和引用。
相关函数
deque.push_back(elem); // 在容器尾部添加一个数据
deque.push_front(elem); // 在容器头部插入一个数据
deque.pop_back(); // 删除容器最后一个数据
deque.pop_front(); // 删除容器第一个数据
deque.at(idx); // 返回索引idx 所指的数据,如果idx 越界,抛出out_of_range 。
deque[idx]; // 返回索引idx 所指的数据,如果idx 越界,不抛出异常,直接出错。
deque.front(); // 返回第一个数据。
deque.back(); // 返回最后一个数据
4. stack
stack 是简单地装饰deque 容器而成为另外的一种容器。
LIFO堆栈
堆栈是一种容器适配器,专门设计用于在LIFO上下文中操作(后进先出),其中元素仅从容器的一端插入和提取。
堆栈实现为容器适配器,它是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。 元素从特定容器的“后面”推出/弹出,该容器称为堆栈顶部。
底层容器可以是任何标准容器类模板或一些其他专门设计的容器类。
相关函数
stack.push(elem); // 往栈头添加元素
stack.pop(); // 从栈头移除第一个元素
stack.empty(); // 判断堆栈是否为空
stack.size(); // 返回堆栈的大小
5.1 queue
FIFO队列
队列是一种容器适配器,专门设计用于在FIFO上下文中操作(先进先出),其中元素插入容器的一端并从另一端提取。
队列实现为容器适配器,它是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。 元素被推入特定容器的“后面”并从其“前面”弹出。
底层容器可以是标准容器类模板之一或其他一些专门设计的容器类。
相关函数
queue.push(elem); // 往队尾添加元素
queue.pop(); // 从队头移除第一个元素
queue.empty(); // 判断队列是否为空
queue.size(); // 返回队列的大小
queue.back(); // 返回最后一个元素
queue.front(); // 返回第一个元素
5.2 priority_queue
优先队列
优先级队列是一种容器适配器,根据一些严格的弱排序标准,专门设计使其第一个元素始终是它包含的最大元素。
此容器类似于堆,其中可以随时插入元素,并且只能检索最大堆元素(优先级队列中顶部的元素)。
优先级队列实现为容器适配器,它是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“后面”弹出,该容器称为优先级队列的顶部。
底层容器可以是任何标准容器类模板或一些其他专门设计的容器类。容器应通过随机访问迭代器访问。
相关函数
priority_queue<int> p1; // 默认是最大值优先级队列
//priority_queue<int, vector<int>, less<int>> p1; // 相当于这样写
priority_queue<int, vector<int>, greater<int>> p2; // 最小值优先级队列
6. list
列表是序列容器,允许在序列中的任何位置进行恒定时间插入和擦除操作,并在两个方向上进行迭代。
列表容器实现为双向链表;双向链表可以将它们包含的每个元素存储在不同且不相关的存储位置中。排序由内部保留,链接到前面元素的链接的每个元素,以及到它后面的元素的链接。
它们与forward_list非常相似:主要区别在于forward_list对象是单链接列表,因此它们只能向前迭代,以换取更小和更高效。
与其他基本标准序列容器(数组,向量和双端队列)相比,列表在插入,提取和移动已经获得迭代器的容器内的任何位置中的元素时通常表现更好,因此也在使用密集型的算法中其中,就像排序算法一样。
与其他序列容器相比,list和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开头或结尾)迭代到该位置,这将在这些位置之间的距离中采用线性时间。它们还消耗一些额外的内存来保持与每个元素相关联的链接信息(这可能是大型小元素列表的重要因素)。
相关函数
list.push_back(elem); // 在容器尾部加入一个元素
list.pop_back(); // 删除容器中最后一个元素
list.push_front(elem); // 在容器开头插入一个元素
list.pop_front(); // 从容器开头移除第一个元素
7.1 set
集合是按特定顺序存储唯一元素的容器。
在集合中,元素的值也标识它(值本身是类型T的键),并且每个值必须是唯一的。 集合中元素的值不能在容器中修改一次(元素总是const),但可以在容器中插入或删除它们。
在内部,集合中的元素总是按照其内部比较对象(类型比较)指示的特定严格弱排序标准进行排序。
set容器通常比unordered_set容器慢,以便通过键访问单个元素,但它们允许根据子集的顺序直接迭代子集。
集合通常实现为二叉搜索树。
7.2 multiset
multiset是按特定顺序存储元素的容器,多个元素可以具有等效值。
在multiset中,元素的值也标识它(值本身是T类型的键)。 多个集合中元素的值不能在容器中修改一次(元素总是const),但可以在容器中插入或删除它们。
在内部,多集合中的元素总是按照其内部比较对象(类型比较)指示的特定严格弱排序标准进行排序。
multiset容器通常比unordered_multiset容器慢,以便通过其键访问单个元素,但它们允许根据子集的顺序直接迭代子集。
multiset通常实现为二叉搜索树。
8.1 map
map是关联容器,它按照特定顺序存储由键值和映射值的组合形成的元素。
在映射中,键值通常用于排序和唯一标识元素,而映射值存储与此键关联的内容。键和映射值的类型可能不同,并在成员类型value_type中组合在一起,这是一种结合两者的对类型:
typedef pair <const Key,T> value_type;
在内部,map中的元素总是按照其内部比较对象(类型比较)指示的特定严格弱排序标准按其键排序。
map容器通常比unordered_map容器慢,以便通过键访问单个元素,但它们允许根据子集的顺序直接迭代子集。
可以使用括号运算符((operator [])通过相应的键直接访问映射中的映射值。
map通常实现为二叉搜索树。
8.2 multimap
Multimaps是关联容器,用于存储由键值和映射值的组合形成的元素,遵循特定顺序,并且多个元素可以具有等效键。
在multimap中,键值通常用于排序和唯一标识元素,而映射值存储与此键关联的内容。 键和映射值的类型可能不同,并在成员类型value_type中组合在一起,这是一种结合两者的对类型:
typedef pair <const Key,T> value_type;
在内部,多图中的元素总是按照其内部比较对象(类型比较)指示的特定严格弱排序标准按其键排序。
multimap容器通常比unordered_multimap容器慢,以通过其键访问单个元素,但它们允许根据子集的顺序直接迭代子集。
multimap通常实现为二叉搜索树。
以下是 C++11 新增的
9. array
数组是固定大小的序列容器:它们包含以严格线性序列排序的特定数量的元素。
在内部,数组不保留除其包含的元素之外的任何数据(甚至不是它的大小,这是一个模板参数,在编译时固定)。它在存储大小方面与使用语言括号语法([])声明的普通数组一样高效。这个类只是为它添加了一层成员函数和全局函数,因此数组可以用作标准容器。
与其他标准容器不同,数组具有固定大小,并且不通过分配器管理其元素的分配:它们是封装固定大小元素数组的聚合类型。因此,它们不能动态扩展或收缩(参见可扩展的类似容器的向量)。
零大小的数组是有效的,但不应该取消引用它们(成员前面,后面和数据)。
与标准库中的其他容器不同,交换两个数组容器是一种线性操作,涉及单独交换范围中的所有元素,这通常是效率相当低的操作。另一方面,这允许两个容器中的元素的迭代器保持其原始容器关联。
数组容器的另一个独特功能是它们可以被视为元组对象:<array>头重载get函数以访问数组的元素,就像它是一个元组一样,以及专门的tuple_size和tuple_element类型。
10. forward_list
前向列表是序列容器,允许在序列中的任何位置进行恒定时间插入和擦除操作。
forward_list实现为单链表;单个链接列表可以将它们包含的每个元素存储在不同且不相关的存储位置中。通过关联到序列中下一个元素的链接的每个元素来保持排序。
forward_list容器和list容器之间的主要设计区别在于,第一个容器内部仅保留指向下一个元素的链接,而后者每个元素保留两个链接:一个指向下一个元素,一个指向前一个元素,从而有效在两个方向上迭代,但每个元素消耗额外的存储空间,并且插入和移除元素的时间略微增加。因此,forward_list对象比列表对象更有效,尽管它们只能向前迭代。
与其他基本标准序列容器(数组,向量和双端队列)相比,forward_list通常在容器内的任何位置插入,提取和移动元素时执行得更好,因此也可以在大量使用这些元素的算法中执行,例如排序算法。
与其他序列容器相比,forward_lists和list的主要缺点是它们无法通过位置直接访问元素;例如,要访问forward_list中的第六个元素,必须从开头到该位置进行迭代,这需要在它们之间的距离内采用线性时间。它们还消耗一些额外的内存来保持与每个元素相关联的链接信息(这可能是大型小元素列表的重要因素)。
forward_list类模板的设计考虑了效率:通过设计,它与简单的手写C风格单链表一样高效,实际上是唯一一个为了提高效率而故意缺乏大小成员函数的标准容器:由于其作为链接列表的性质,具有持续时间的大小成员将要求它保持其大小的内部计数器(如列表所示)。这将消耗一些额外的存储空间,并使插入和移除操作的效率略低。要获取forward_list对象的大小,可以使用距离算法及其开始和结束,这是一个需要线性时间的操作。
11.1 unordered_set
unordered_set是不按特定顺序存储唯一元素的容器,它允许根据单个元素的值快速检索单个元素。
在unordered_set中,元素的值同时是其键,它唯一地标识它。 键是不可变的,因此,unordered_set中的元素不能在容器中修改一次 - 但是可以插入和删除它们。
在内部,unordered_set中的元素不按任何特定顺序排序,而是根据其哈希值组织到存储桶中,以允许直接通过其值快速访问各个元素(平均时间复杂度恒定)。
unordered_set容器比通过其键访问单个元素的set容器更快,尽管它们通过其元素子集的范围迭代通常效率较低。
容器中的迭代器至少是前向迭代器。
11.2 unordered_multiset
unordered_multiset是不按特定顺序存储元素的容器,允许基于其值快速检索单个元素,非常类似于unordered_set容器,但允许不同元素具有等效值。
在unordered_multiset中,元素的值同时是其键,用于标识它。键是不可变的,因此,unordered_multiset中的元素不能在容器中修改一次 - 但是可以插入和删除它们。
在内部,unordered_multiset中的元素没有按任何特定方式排序,而是根据其哈希值组织到存储桶中,以允许直接通过其值快速访问各个元素(平均时间复杂度恒定)。
具有等效值的元素在同一个存储桶中组合在一起,并且迭代器(请参阅equal_range)可以迭代所有这些元素。
容器中的迭代器至少是前向迭代器。
请注意,此容器未在其自己的标头中定义,但使用unordered_set共享标头<unordered_set>。
12.1 unordered_map
unordered_map是关联容器,其存储由键值和映射值的组合形成的元素,并且允许基于其键快速检索各个元素。
在unordered_map中,键值通常用于唯一标识元素,而映射值是具有与此键关联的内容的对象。键和映射值的类型可能不同。
在内部,unordered_map中的元素没有按照其键值或映射值以任何特定顺序排序,而是根据其哈希值组织到桶中,以允许通过键值直接快速访问各个元素(使用常量)平均时间复杂度)。
unordered_map容器比映射容器更快地通过其键访问单个元素,尽管它们通过其元素子集的范围迭代通常效率较低。
unordered_map实现直接访问运算符(operator []),允许使用其键值作为参数直接访问映射值。
容器中的迭代器至少是前向迭代器。
12.2 unordered_multimap
unordered_multimap是关联容器,它存储由键值和映射值组合形成的元素,非常类似于unordered_map容器,但允许不同的元素具有等效键。
在unordered_multimap中,键值通常用于唯一标识元素,而映射值是具有与此键关联的内容的对象。键和映射值的类型可能不同。
在内部,unordered_multimap中的元素没有按照其键值或映射值以任何特定顺序排序,而是根据其哈希值组织到桶中,以允许通过其键值直接快速访问各个元素(使用常量)平均时间复杂度)。
具有等效键的元素在同一个桶中组合在一起,并且迭代器(请参阅equal_range)可以遍历所有这些元素。
容器中的迭代器至少是前向迭代器。
请注意,此容器未在其自己的标头中定义,但与unordered_map共享标头<unordered_map>。