C++ 标准模板库(STL)是 C++ 的一个重要组成部分,它提供了许多通用数据结构和算法,用于简化和加速程序开发。STL 主要由容器、算法和迭代器组成,下面是对这些主要部分的详细介绍:
1. 容器(Containers)
容器是用来存储数据的数据结构,STL 提供了多种容器,每种容器都有其特定的用途和性能特点。
序列容器(Sequence Containers):
std::vector
: 动态数组,支持快速随机访问和动态增删元素。
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);
for (int i : vec) {
std::cout << i << " ";
}
return 0;
}
std::deque
: 双端队列,支持在两端高效地插入和删除元素。
#include <deque>
#include <iostream>
int main() {
std::deque<int> deq = {1, 2, 3, 4, 5};
deq.push_front(0);
deq.push_back(6);
for (int i : deq) {
std::cout << i << " ";
}
return 0;
}
std::list
: 双向链表,支持在任意位置高效地插入和删除元素。
#include <list>
#include <iostream>
int main() {
std::list<int> li = {1, 2, 3, 4, 5};
li.push_front(0);
li.push_back(6);
for (int i : li) {
std::cout << i << " ";
}
return 0;
}
std::forward_list
: 单向链表,与 std::list
类似,但只能单向遍历。
#include <forward_list>
#include <iostream>
int main() {
std::forward_list<int> flist = {1, 2, 3, 4, 5};
flist.push_front(0);
for (int i : flist) {
std::cout << i << " ";
}
return 0;
}
-
关联容器(Associative Containers):
std::set:
有序集合,元素按照键值排序,不允许重复元素。
#include <set>
#include <iostream>
int main() {
std::set<int> myset = {3, 1, 4, 1, 5, 9};
for (int i : myset) {
std::cout << i << " ";
}
return 0;
}
std::multiset
:多重集容器,可以存储重复的元素,并按照升序进行排序
#include <set>
#include <iostream>
int main() {
std::multiset<int> myset = {3, 1, 4, 1, 5, 9};
for (int i : myset) {
std::cout << i << " ";
}
return 0;
}
std::map
: 有序映射,键值对按照键排序,不允许重复键。
#include <map>
#include <iostream>
#include <string>
int main() {
std::map<int, std::string> mymap = {{1, "one"}, {2, "two"}, {3, "three"}};
for (auto& pair : mymap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
std::multimap:
#include <map>
#include <iostream>
#include <string>
int main() {
std::multimap<int, std::string> mymap = {{1, "one"}, {2, "two"}, {1, "uno"}};
for (auto& pair : mymap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
-
无序容器(Unordered Containers):
std::unordered_set
, std::unordered_multiset
: 无序集合,元素无序存储,不允许、允许重复元素。
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> myset = {3, 1, 4, 1, 5, 9};
for (int i : myset) {
std::cout << i << " ";
}
return 0;
}
std::unordered_map
, std::unordered_multimap
: 无序映射,键值对无序存储,不允许、允许重复键。
#include <unordered_map>
#include <iostream>
#include <string>
int main() {
std::unordered_map<int, std::string> mymap = {{1, "one"}, {2, "two"}, {3, "three"}};
for (auto& pair : mymap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
2. 算法(Algorithms)
STL 提供了一系列通用算法,用于对容器中的元素执行各种操作,例如查找、排序、转换等。
- 查找:
std::find
,std::find_if
,std::binary_search
等。 - 排序:
std::sort
,std::stable_sort
,std::partial_sort
等。 - 数值算法:
std::accumulate
,std::transform
,std::inner_product
等。 - 堆算法:
std::make_heap
,std::push_heap
,std::pop_heap
等。 - 集合操作:
std::merge
,std::set_union
,std::set_intersection
等。
其中经典的增删改查算法复杂度:
-
std::vector:
- 插入(Insertion):
- 在末尾插入:平均情况下是常数时间复杂度,因为在末尾插入只需要将元素追加到底层数组的末尾,但如果数组大小不够,则需要重新分配内存,将现有元素复制到新内存中,时间复杂度为 O(n)。
- 在中间或开头插入:需要将插入位置之后的元素向后移动,平均时间复杂度为 O(n)。
- 查找(Lookup):通过索引直接访问元素,时间复杂度为 O(1)。
- 删除(Deletion):
- 在末尾删除:平均情况下是常数时间复杂度,因为只需要移除最后一个元素。
- 在中间或开头删除:需要将删除位置之后的元素向前移动,平均时间复杂度为 O(n)。
- 插入(Insertion):
-
std::deque:
- 插入(Insertion):
- 在两端插入:在两端插入操作的时间复杂度为常数,因为 deque 是双端队列,支持快速在两端进行插入操作。
- 在中间插入:需要对中间位置之后的元素进行移动,平均时间复杂度为 O(n)。
- 查找(Lookup):通过索引直接访问元素,时间复杂度为 O(1)。
- 删除(Deletion):
- 在两端删除:在两端删除操作的时间复杂度为常数。
- 在中间删除:需要对删除位置之后的元素进行移动,平均时间复杂度为 O(n)。
- 插入(Insertion):
-
std::list:
- 插入(Insertion):在任意位置插入元素的时间复杂度为常数,因为它是双向链表,插入只需要调整指针。
- 查找(Lookup):需要遍历链表来查找元素,平均时间复杂度为 O(n)。
- 删除(Deletion):在任意位置删除元素的时间复杂度为常数,因为它是双向链表,删除只需要调整指针。
-
std::forward_list:
- 插入(Insertion):在头部插入元素的时间复杂度为常数,因为它是单向链表,插入只需要调整指针。
- 查找(Lookup):需要遍历链表来查找元素,平均时间复杂度为 O(n)。
- 删除(Deletion):在任意位置删除元素的时间复杂度为常数,因为它是单向链表,删除只需要调整指针。
-
std::set:
- 插入(Insertion):O(log n),其中 n 是集合中元素的数量。插入时需要确保元素的顺序。
- 查找(Lookup):O(log n),通过红黑树实现,具有对数时间复杂度。
- 删除(Deletion):O(log n),通过红黑树实现,具有对数时间复杂度。
-
std::multiset:
- 插入(Insertion):O(log n),通过红黑树实现,具有对数时间复杂度。
- 查找(Lookup):O(log n),通过红黑树实现,具有对数时间复杂度。
- 删除(Deletion):O(log n),通过红黑树实现,具有对数时间复杂度。
-
std::map:
- 插入(Insertion):O(log n),通过红黑树实现,具有对数时间复杂度。
- 查找(Lookup):O(log n),通过红黑树实现,具有对数时间复杂度。
- 删除(Deletion):O(log n),通过红黑树实现,具有对数时间复杂度。
-
std::multimap:
- 插入(Insertion):O(log n),通过红黑树实现,具有对数时间复杂度。
- 查找(Lookup):O(log n),通过红黑树实现,具有对数时间复杂度。
- 删除(Deletion):O(log n),通过红黑树实现,具有对数时间复杂度。
-
std::unordered_set:
- 插入(Insertion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是集合中元素的数量。
- 查找(Lookup):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是集合中元素的数量。
- 删除(Deletion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是集合中元素的数量。
-
std::unordered_multiset:
- 插入(Insertion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重集合中元素的数量。
- 查找(Lookup):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重集合中元素的数量。
- 删除(Deletion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重集合中元素的数量。
-
std::unordered_map:
- 插入(Insertion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是映射中元素的数量。
- 查找(Lookup):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是映射中元素的数量。
- 删除(Deletion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是映射中元素的数量。
-
std::unordered_multimap:
- 插入(Insertion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重映射中元素的数量。
- 查找(Lookup):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重映射中元素的数量。
- 删除(Deletion):平均时间复杂度为 O(1),最坏情况下为 O(n),其中 n 是多重映射中元素的数量。
3. 迭代器(Iterators)
STL(标准模板库)的迭代器是一种泛型的指针,用于遍历容器(如向量、列表、集合等)中的元素,是STL算法和容器之间的桥梁。迭代器提供了一种统一的访问容器元素的方式,使得算法可以独立于容器类型而工作。
迭代器分类:
STL的迭代器可以根据其功能和能力分为不同的类型,主要分为输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。这些迭代器的功能和支持的操作在类型之间有所不同,具体如下:
-
输入迭代器(Input Iterator):
- 支持逐个读取容器中的元素。
- 只能逐个向前移动,并且只能进行一次遍历。
- 不能修改容器中的元素。
-
输出迭代器(Output Iterator):
- 支持逐个写入容器中的元素。
- 只能逐个向前移动,并且只能进行一次遍历。
- 不能读取容器中的元素。
-
前向迭代器(Forward Iterator):
- 具有输入迭代器的所有功能。
- 支持多次遍历容器,但只能向前移动。
- 能够逐个读取和修改容器中的元素。
-
双向迭代器(Bidirectional Iterator):
- 具有前向迭代器的所有功能。
- 支持向前和向后移动。
- 通常用于双向链表等数据结构。
-
随机访问迭代器(Random Access Iterator):
- 具有双向迭代器的所有功能。
- 支持随机访问容器中的元素,可以跳跃式地移动迭代器。
- 支持比较、加法、减法等操作,具有指针的功能。
迭代器的操作:
迭代器提供了一系列操作来遍历容器中的元素,包括:
*it
:解引用迭代器,获取迭代器指向的元素。it++
或++it
:后置递增和前置递增,使迭代器指向容器中的下一个元素。it--
或--it
:后置递减和前置递减,使迭代器指向容器中的前一个元素。it1 + n
或it1 - n
:随机访问迭代器支持跳跃式移动,使迭代器向前或向后移动 n 个位置。it1 == it2
和it1 != it2
:比较两个迭代器是否相等或不相等。
迭代器的适用范围:
不同类型的迭代器适用于不同类型的容器。例如,随机访问迭代器适用于数组和向量等支持随机访问的容器,而输入迭代器适用于流(例如文件流)等只能顺序访问的容器。选择合适的迭代器类型可以提高算法的效率和灵活性。
总的来说,STL的迭代器是一种强大的工具,为算法和容器提供了一种通用的访问方式,使得用户能够更加灵活地处理数据结构中的元素。
4. 分配器:
分配器是一种用于管理容器内存分配和释放的对象,它定义了容器在创建、销毁、复制和重新分配内存时的行为。STL容器在默认情况下使用标准分配器 std::allocator
,但是用户也可以自定义分配器以满足特定的需求,例如性能优化、内存池管理等。
自定义分配器的步骤:
- 定义分配器类:自定义一个类,该类要符合 Allocator 的要求,并提供内存分配和释放的操作符。
- 在容器声明时指定分配器:在声明容器时,通过模板参数显式指定使用的分配器类型。
#include <iostream>
#include <vector>
#include <memory>
// 自定义分配器类
template<typename T>
class MyAllocator {
public:
using value_type = T;
MyAllocator() noexcept = default;
template<typename U>
MyAllocator(const MyAllocator<U>&) noexcept {}
T* allocate(std::size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* ptr, std::size_t) noexcept {
::operator delete(ptr);
}
};
int main() {
// 使用自定义分配器的 vector
std::vector<int, MyAllocator<int>> vec;
vec.push_back(1);
vec.push_back(2);
for (int num : vec) {
std::cout << num << " ";
}
return 0;
}
5. 适配器:
适配器是一种用于修改容器行为的对象,它可以在不修改原始容器的情况下改变其行为或提供新的功能。STL中提供了多种适配器,其中最常见的是容器适配器和迭代器适配器。
容器适配器:
容器适配器是一种修改容器行为的对象,常见的容器适配器包括栈(stack)、队列(queue)和优先队列(priority_queue),它们通过修改底层容器的行为来提供新的功能。
迭代器适配器:
迭代器适配器是一种修改迭代器行为的对象,常见的迭代器适配器包括反向迭代器(reverse_iterator)、插入迭代器(insert_iterator)、流迭代器(stream_iterator)等,它们通过修改迭代器的行为来实现不同的遍历方式或操作方式。