C++ STL基础

@[toc]
本文将对C++的STL进行一些基础介绍。

STL简介

STL(Standard Template Library,标准模板库),顾名思义是一堆C模板及其支持工具所组成的库,由于STL基本占据了C标准库的绝大部分内容,所以有时也用STL指代C++标准库。

STL开源实现:

STL主要包含6大组件:

  1. 容器(containers):各种数据结构,是一种class template
  2. 算法(algorithms):各种常用算法,是一种function template
  3. 迭代器(iterators):连接容器和算法,是所谓的“泛型指针”
    • 输入迭代器
    • 输出迭代器
    • 前向迭代器
    • 双向迭代器
    • 随机访问迭代器
  4. 仿函数(functors):行为类似函数,可作为算法的某种策略。从实现角度看,仿函数是一种重载了operator()的class或者class template,一般的函数指针可以视为狭义的仿函数。
  5. 适配器(adapters):一种用来修饰容器或者仿函数或者迭代器接口的东西。
  6. 配置器(allocators):复制空间配置和管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。
    这6大组件之间的关系如下:
    在这里插入图片描述
  • 容器作为STL的主体,是许多不同的数据结构
  • 分配器为容器的实现分配应有的空间
  • 泛型算法用来处理容器中的数据
  • 迭代器是泛型算法和容器之间的粘合剂
  • 仿函数使得算法可以有更加灵活的自定义模式
  • 适配器保证了自定义的功能可以和STL中现有的功能相融合

STL容器

顺序容器

A是分配器选项,默认值为std::allocator<T>

vector<T,A>:空间连续分配的T 类型元素序列;默认选择容器
List<T,A>:T类型元素双向链表;当需要插入/删除元素当不移动已有元素时选择
forward_list<T,A>:T类型元素单向链表;很短的或空序列的理想选择
deque<T,A>:T类型元素双端队列;向量和链表的混合;对大多数应用而言,都比向量和链表要慢

std::forward_list 是 C++11 引入的一个容器,属于 STL(标准模板库)的一部分。它是一种单向链表的实现,具有一些独特的特点和适用场景。
特点:

  • 单向链表:与 stdlist(双向链表)不同,stdforward_list 只维护对第一个元素的指针,没有反向指针,因此只能从头遍历到尾,而不能反向遍历。
  • 内存效率:由于只存储单向指针,stdforward_list 在内存占用上比 stdlist 更为节省,适用于需要大量节点但内存资源有限的情况。
  • 动态大小:可以动态地增加和删除元素,容量不固定。
  • 插入和删除:插入和删除操作的复杂度为 O(1),但需要知道插入或删除位置的前一个元素。
  • 没有 random access:不支持随机访问,因此不能使用下标访问元素。
  • Iterators:支持前向迭代器,可以用来遍历元素,但不支持双向迭代。
  • 存储最少的额外信息:由于结构的简化,std::forward_list 在处理大量元素时,能提供更好的性能表现。

使用场景:

  • 内存敏感的应用:在需要对大量元素进行频繁插入或删除操作时,并且对内存效率有要求的场合。
  • 顺序数据存储:当只需要按顺序存储数据,且不需要随机访问时,std::forward_list 是一个合适的选择。
  • 实现某些算法:某些链式数据结构的算法(如合并、分割)在 std::forward_list 中可能更为高效,因为它能够通过简单的指针操作快速操作元素。
  • 历史数据处理:适用于需要保持数据版本历史的场合,能够通过简单的插入和删除操作对历史数据进行管理。
  • 实现 Stack/Queue:可以用 std::forward_list 来实现简单的堆栈或队列数据结构,因为这些结构主要依赖于元素的插入和删除。

总的来说,std::forward_list 是一个轻量级且高效的链表实现,适合于处理大量顺序数据的场景,尤其是在内存管理和插入删除效率有要求时。

stdlist 和 stdvector的区别:

  1. 数据结构
    stdvector: 基于动态数组,所有元素存储在连续的内存中。
    std
    list: 基于双向链表,每个元素(节点)包含指向前一个和后一个元素的指针。
  2. 存储方式
    stdvector: 所有元素在内存中是连续的,因此可以直接通过索引访问 (O(1)),这也使得它在缓存利用上表现较好。
    std
    list: 元素存储在不同的位置,因此不能通过索引直接访问,访问元素需要 O(n) 的时间复杂度。
  3. 插入和删除
    stdvector: 在末尾插入元素非常高效 (O(1))。但在中间或开头插入和删除元素需要移动其他元素,因此时间复杂度为 O(n)。
    std
    list: 在任意位置插入或删除元素都很高效,时间复杂度为 O(1),前提是你已经有要插入或删除元素的迭代器。
  4. 容量
    stdvector: 有大小(size)和容量(capacity)的概念,容量是分配的内存大小,可能比实际大小大,当需要更多空间时,会进行重新分配,可能导致性能下降(尤其是对于大量插入)。
    std
    list: 不需要考虑容量的问题,因为每个节点是独立分配内存的。
  5. 适用场景
    stdvector: 适合于频繁随机访问、顺序遍历、末尾插入和删除的场景。
    std
    list: 适合于频繁插入和删除的场景,特别是在数据结构的中间或开头,但不推荐频繁随机访问的场景。
  6. 内存占用
    stdvector: 在内存中更紧凑,因为存储在连续的内存区域,额外的内存开销较小。
    std
    list: 每个元素需要额外的内存来存储指向前后元素的指针,因此总体内存开销较大。
    总结
    选择使用 stdvector 还是 stdlist 取决于具体的使用场景以及性能需求。若需要高效的随机访问和缓存友好的特性,stdvector 是更好的选择;而如果需要频繁的插入和删除操作,尤其是在容器的中间部分,stdlist 则更为合适。

deque和list的区别:

  • deque

    特点:
    支持快速的随机访问(与 vector 类似)。
    可以在两端高效地插入和删除元素。
    使用动态数组实现,可能会在内存中分配多个非连续块。

    优点:
    适合需要频繁在两端插入和删除的场景,例如双端队列、缓冲区等。
    支持 O(1) 时间复杂度的访问和修改。

    缺点:
    相比于 vector,在元素数量变化时可能会有较大的内存开销。
    不支持移动构造和移动赋值等操作,性能上可能略逊一筹。

  • list

    特点:
    实现为双向链表,支持在任意位置的快速插入和删除。
    不支持随机访问。

    优点:
    在插入和删除操作时,不需要移动其他元素,因此在频繁插入删除的场合性能表现优秀。
    可以很容易地实现一些需求,比如迭代器的操作,不会因为元素数量变化而导致操作复杂度上升。

    缺点:
    不支持高效的随机访问,访问元素的时间复杂度是 O(n)。
    每个节点需要额外的指针存储,导致空间效率较低。

总结:
当我需要频繁在容器两端进行插入和删除操作时,deque 是一个不错的选择,因为它同时支持快速访问和两端操作。
如果需要在容器的中间频繁插入和删除元素,并且不太关注访问性能,我更倾向于使用 list。
对性能有要求的项目中,尤其需要关注具体的使用场景和操作的性质,选择合适的容器以获得最佳的效率。

有序关联容器

C是比较类型,A是分配器选项

map<K,V,C,A>:从K到V的有序映射;一个(k,V)对序列
multimap<K,V,C,A>:从K到V的有序映射;允许重复关键字
set<K,C,A>:K的有序集合
multiset<K,C,A>:K的有序集合;允许重复关键字

这些容器通常用平衡二叉树(通常是红黑树)实现,关键字K的默认序标准是std::less<K>,对映射,A的默认值为std::allocator<std::pair<const K, T>>,对集合A的默认值为std::allocator<K>

在C++中,map、set、multimap和multiset都是标准模板库(STL)中非常重要的关联容器。它们的主要区别在于它们存储的数据类型、键值的唯一性以及如何存储重复元素。下面是这几种容器的基本特点和区别:
map:

  • 定义: 一种关联容器,用于存储键值对(key-value pairs)。
  • 唯一性: 键是唯一的,不能重复。如果插入一个已存在的键,新值将覆盖旧值。
  • 排序: 根据键的顺序自动排序(通常是升序),可以使用自定义的比较函数。
  • 访问: 通过键快速查找值。
    std::map<int, std::string> myMap;
    myMap[1] = "one"; // 插入
    myMap[2] = "two";

set:

  • 定义: 一种关联容器,用于存储唯一的值。
  • 唯一性: 不允许重复元素。
  • 排序: 存储元素会自动排序(通常是升序)。
  • 存在性检查: 可以快速检查一个元素是否存在于集合中。
    std::set<int> mySet;
    mySet.insert(1); // 插入
    mySet.insert(2);

multimap:

  • 定义: 一种关联容器,用于存储键值对,但允许键重复。
  • 唯一性: 键可以重复,可以存储多个相同键的元素。
  • 排序: 同样根据键的顺序自动排序。
  • 访问: 可以通过键查找对应的所有值。
    std::multimap<int, std::string> myMultiMap;
    myMultiMap.insert({1, "one"});
    myMultiMap.insert({1, "first"}); // 允许重复键

multiset:

  • 定义: 一种关联容器,用于存储值,但允许重复元素。
  • 唯一性: 可以存储多个相同的元素。
  • 排序: 会自动排序(通常是升序)。
  • 计数: 可以用于统计某个元素的出现次数。
    std::multiset<int> myMultiSet;
    myMultiSet.insert(1);
    myMultiSet.insert(1); // 允许重复元素

总结来说:

  • map 和 set 中的元素(键)是唯一的,而 multimap 和 multiset 允许重复。
  • map 存储键值对,set 仅存储唯一元素。
  • multimap 和 multiset 则是前两者的扩展,允许重复的键或者元素。

无序关联容器

H是哈希函数类型,E是相等性测试,A是分配器类型

unordered_map<K,V,H,E,A>:从K到V的无序映射
unordered_multimap<K,V,H,E,A>:从K到V的无序映射;允许重复关键字
unordered_set<K,H,E,A>:K的无序集合
unordered_multiset<K,H,E,A>:K的无序集合;允许重复关键字

这些容器都是采用一处链表法的哈希表实现。关键字类型K的默认哈希哈树类型H为std::hash<K>。关键字类型K的默认相等性判定函数类型E为std::equal_to<K>;相等性判断函数用来判断哈希值相同的两个对象是否相等。

容器适配器

在C++中,adapter容器是指在标准模板库(STL)中那些不直接存储元素,而是通过其他容器来间接存储或访问元素的容器。这样的容器通常用来提供特定的接口或功能,适应不同的需求。常见的适配器容器包括以下几种:

  1. 栈(stack)

    • 栈是一个后进先出(LIFO)的数据结构。STL中的 std::stack 是一个适配器,它通常基于其他容器(如 std::dequestd::vector)来实现栈的操作。
  2. 队列(queue)

    • 队列是一个先进先出(FIFO)的数据结构。STL中的 std::queue 也是一个适配器,通常基于 std::deque 来实现队列操作。
  3. 优先队列(priority_queue)

    • 优先队列是一个每次取出最大(或最小)元素的队列。STL中的 std::priority_queue 利用底层容器(通常是 std::vector)来维护元素的顺序,以支持优先级操作。

这些适配器容器主要提供了特定的容器接口,允许你以一种特定的方式访问和操作底层容器中的数据,而不需要直接与底层容器的细节打交道。这种设计使得代码更加灵活且易于维护。

C是一个容器类型

priority_queue<T,C,Cmp>:T的优先队列;Cmp是优先级函数类型
queue<T,C>:T的队列,支持push和pop操作
stack<T,C>:T的栈,支持push和pop操作

priority_queue的默认优先级函数Cmp是std::less<T>,它的底层是容器加heap。queue的默认容器类型C为std::deque<T>,stack和priority_queue的默认容器类型C为std::vector<T>

容器适配器(container adaptor)为容器提供不同(通常受限)的接口。容器适配器的实际用法就是仅通过其特殊接口使用。STL容器适配器不提供直接访问其底层容器的方式,也不提供迭代器或下标操作

priority_queue的应用场景:

  • 任务调度:
    在操作系统中,priority_queue 可以用于管理任务的优先级,使高优先级的任务能够优先执行。
  • 算法中的贪心策略:
    在一些贪心算法(如赫夫曼编码)中,需要频繁地获取当前最小或最大元素。priority_queue 能够高效地支持这种需求。
  • 图算法:
    Dijkstra 算法和 Prim 算法都可以使用 priority_queue 来管理边或节点的优先级,从而实现有效的最短路径或最小生成树的计算。
  • 事件模拟:
    在离散事件模拟中,事件通常根据时间戳发生在特定的时刻。使用 priority_queue 可以方便地管理即将发生的事件,从而模拟系统的行为。
  • 动态数据流处理:
    对于需要不断处理和更新的数据流(如维护一个实时的中位数),可以使用两个 priority_queue 来分别管理较小一半和较大一半的数据。
  • 最小/最大k选取问题:
    当需要找到一组数据中的前k个最大或最小元素时,使用 priority_queue 可以高效地处理。
  • 合并多个有序序列:
    在合并多个有序列表或数组时,可以使用 priority_queue 来保持当前最小的元素,以便于构建最终的有序输出。

拟容器

某些数据类型具有标准容器所应有的大部分特性,但有非全部,称这些容器为拟容器。

T[N]:固定大小的内置数组;没有size()或其他成员函数
array<T,N>:固定大小的数组;类似内置数组,但解决了大部分问题
basic_string<C,Tr,A>:一个连续分配空间的类型为C的字符序列,支持文本处理操作;A是分配器,Tr是字符萃取。basic_string通常经过优化,短字符串无须使用自由存储空间。
stringbasic_string<char>
u16stringbasic_string<char16_t>
u32stringbasic_string<char32_t>
wstringbasic_string<wchar_t>
valarray<T>:数值向量,支持向量运算
bitset<N>:N个二进制位的集合,支持集合操作,如&|
vector<bool>vector<T>的特化版本,紧凑保存二进制位

stdvector 和 stdarray 是 C++ 中两个常用的容器类型,它们在功能和使用场景上有一些重要的区别:

  1. 大小
    stdvector:
    动态大小:可以在运行时改变大小。可以使用 push_back 添加元素,使用 resize 调整大小。
    std
    array:
    固定大小:在编译时定义大小,且大小在整个生命周期中保持不变。一旦定义,无法更改。
  2. 内存管理
    stdvector:
    动态分配内存:在堆上分配内存,可能会导致性能开销(如分配和释放内存时)。
    std
    array:
    静态分配内存:在栈上分配内存,更高效且不开销,内存的使用更简单。
  3. 功能
    stdvector:
    支持许多有用的方法(如 insert、erase、clear 等)来管理动态数据。
    可以轻松处理大规模且成长动态的数据集。
    std
    array:
    提供的功能较少,主要用作固定大小数组的替代品。更多的适合在需要固定数量元素的场景中使用。
  4. 性能
    stdvector:
    动态内存分配可能会引起性能问题,当进行多次插入或删除时可能会导致内存重新分配。
    std
    array:
    相对更高效,因为其大小是固定的,没有动态内存分配的开销。
  5. 语法和使用方式
    stdvector 在使用时需要更复杂的操作(如进行内存管理),而 stdarray 更加接近于传统的 C 风格数组的使用方式。
    总结
    使用 stdvector 当你需要一个可以动态改变大小的数组。
    使用 std
    array 当你知道数组的大小在编译时,并且不需要动态修改时。

C++11中的std::array相比于C风格数组,具有多个优势。下面是一些重要的优势以及相应的示例:

  1. 类型安全
    std::array是一个模板类,能够确保数组类型的一致性,并且在编译时进行类型检查。这避免了C风格数组易于引起的类型错误。
#include <array>
#include <iostream>

std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
// arr1[0] 是 int 类型

// 如果要将其设置为double类型,则无法编译通过
// std::array<double, 5> arr2 = arr1;  // 错误:类型不匹配
  1. 尺寸的固定性
    std::array的大小是在编译时确定的,且是强制的,提供了更好的可读性和维护性。
std::array<int, 5> myArray = {1, 2, 3, 4, 5};
// 尺寸固定,不可以更改
  1. 提供成员函数
    std::array提供了成员函数,如size()和at(),可以提高程序的可读性和安全性。
#include <array>
#include <iostream>

std::array<int, 5> arr = {10, 20, 30, 40, 50};

std::cout << "Size: " << arr.size() << std::endl; // 输出尺寸
std::cout << "Element at index 2: " << arr.at(2) << std::endl; // 安全访问元素
// std::cout << arr.at(5); // 会抛出异常
  1. 支持 STL 算法
    stdarray可以与标准库中的算法(如 stdsort, std::find 等)一起工作,而C风格数组不支持。
#include <array>
#include <algorithm>
#include <iostream>

std::array<int, 5> arr = {5, 3, 1, 4, 2};

std::sort(arr.begin(), arr.end());

for (const auto& elem : arr) {
    std::cout << elem << " "; // 输出排序后的数组
}
  1. 迭代器支持
    std::array提供了迭代器接口,使遍历更加方便和一致。
#include <array>
#include <iostream>

std::array<int, 5> arr = {1, 2, 3, 4, 5};

for (auto it = arr.begin(); it != arr.end(); ++it) {
    std::cout << *it << " "; // 输出数组元素
}
  1. 兼容性
    std::array可以被用于与标准库函数、算法、容器等互操作,而C风格数组在这方面相对受到限制。

总结
虽然C风格数组提供了简单的数组功能,但std::array的类型安全性、成员函数、STL支持和可读性使其成为现代 C++ 编程中更优的选择。

标准容器操作复杂性

容器 下标访问 任意位置插入和删除 在头部插入和删除 在尾部插入和删除 支持迭代器
vector 常量 O(n)+ 常量+ 随机
list 常量 常量 常量 双向
forward_list 常量 常量 前向
deque 常量 O(n) 常量 常量 随机
stack 常量
queue 常量 常量
priority_queue O(log(n)) O(log(n))
map O(log(n)) O(log(n))+ 双向
multimap O(log(n))+ 双向
set O(log(n))+ 双向
multiset O(log(n))+ 双向
unordered_map 常量+ 常量+ 前向
unordered_multimap 常量+ 前向
unordered_set 常量+ 前向
unordered_multiset 常量+ 前向
string 常量 O(n)+ O(n)+ 常量+ 随机
array 常量 随机
内置数组 常量 随机
valarray 常量 随机
bitset 常量
  1. 序列容器

    • vector

      • 时间复杂度:

        • 插入/删除操作(尾部):O(1)
        • 插入/删除操作(中间):O(n)
        • 随机访问:O(1)
      • 空间复杂度:O(n)(n是元素数量,包含额外的容量空间)

    • deque

      • 时间复杂度:

        • 在两端插入/删除:O(1)
        • 随机访问:O(1)
        • 中间插入/删除:O(n)
      • 空间复杂度:O(n)

    • list

      • 时间复杂度:

        • 插入/删除:O(1)(已知位置)
        • 随机访问:O(n)
      • 空间复杂度:O(n)

  2. 关联容器

    • set/multiset

      • 时间复杂度:

        • 插入/删除/查找:O(log n)(基于红黑树实现)
      • 空间复杂度:O(n)

    • map/multimap

      • 时间复杂度:

        • 插入/删除/查找:O(log n)
      • 空间复杂度:O(n)

  3. 无序关联容器

    • unordered_set/unordered_multiset

      • 时间复杂度:

        • 插入/删除/查找:O(1)(平均情况下),O(n)(最坏情况下)
      • 空间复杂度:O(n)

    • unordered_map/unordered_multimap

      • 时间复杂度:

        • 插入/删除/查找:O(1)(平均情况下),O(n)(最坏情况下)
      • 空间复杂度:O(n)

如何选择STL容器

选择合适的STL(标准模板库)容器时,需要考虑多种因素,包括数据的性质、操作的频率和复杂性。以下是一些选择合适STL容器时的指导原则:

数据存储方式:
    顺序容器(如 vector、deque、list):
        如果需要频繁访问元素,通过索引查找,使用 vector 是一个好选择。
        如果需要在两端快速插入和删除,使用 deque。
        如果需要频繁在任意位置插入和删除,考虑使用 list。

查找和访问效率:
    关联容器(如 set、map、unordered_set、unordered_map):
        如果需要按顺序访问元素,使用 set 或 map。
        如果不需要元素的顺序,且更关注查找速度,选择 unordered_set 或 unordered_map。

插入和删除的复杂性:
    对于频繁的插入和删除操作,list 提供O(1)的时间复杂度,但随机访问性能较差。
    vector 在尾部插入和删除性能较好,但在中间或前端插入和删除可能需要移动元素。

元素个数的动态变化:
    如果预知元素的数量,可以选择 vector,并通过 reserve 减少内存重新分配的次数。
    如果数量变化大且不易预知,考虑 list 或 deque。

内存使用和性能:
    vector 通常比 list 提供更好的内存局部性,性能更佳。
    如果对内存的占用有严格要求,考虑合适的容器,如 vector 通常比较紧凑。

使用场景:
    栈和队列:使用 stack 和 queue 适合后进先出或先进先出的场景,底层实现可以基于 deque 或 vector。
    去重和查重:如果需要确保唯一性,选择 set 或 unordered_set。

总结
vector:适合随机访问、尾部插入和删除。
deque:适合在两端插入/删除。
list:适合频繁插入和删除,但不适合随机访问。
set/map:适合需要保证有序性。
unordered_set/unordered_map:适合需要快速查找而不考虑顺序。

STL算法

标准库算法的目标是为可有化实现的某些东西提供最通用最灵活的接口。
无论一个STL算法返回什么,它都不会是实参的容器。传递给STL算法的实参是迭代器,算法完全不了解迭代器所指向的数据结构。迭代器存在主要是为了将算法从它所处理的数据结构上分离开来。
每个算法基本都有一个使用后缀_if的版本,该版本接受一个谓词,可以指定策略。

以下是一些常用的算法及其分类介绍:

  1. 排序算法
  • std::sort:对给定范围内的元素进行排序。
  • std::stable_sort:与 std::sort 类似,但保持相等元素的原有顺序。
  1. 查找算法
  • std::find:在范围内查找第一个匹配的元素。
  • std::binary_search:在已排序的范围内查找元素,并返回是否存在。
  • std::lower_bound:返回指向范围内第一个不小于给定值的元素的迭代器。
  • std::upper_bound:返回指向范围内第一个严格大于给定值的元素的迭代器。
  1. 修改算法
  • std::copy:将一个范围内的元素复制到另一个范围。
  • std::transform:对范围内的每个元素应用一个操作,并将结果存储到另一范围中。
  • std::remove:移除范围内特定值的元素,返回去除后有效范围的新结束位置。
  1. 合并操作
  • std::merge:合并两个已排序的范围,生成一个新的已排序范围。
  • std::set_union:计算两个集合的并集。
  1. 随机数算法
  • std::shuffle:随机打乱一个范围内的元素顺序。
  1. 归约操作
  • std::accumulate:对范围内的元素进行求和或其他操作。
  • std::count:计算范围内某一特定值的出现次数。
  1. 集合算法
  • std::set_intersection:计算两个集合的交集。
  • std::set_difference:计算一个集合相对于另一个集合的差集。
  1. 其他算法
  • std::for_each:对范围内的每个元素应用指定的操作。
  • std::find_if:查找满足条件的第一个元素。

算法复杂性

大多数算法都是线性时间O(n)的,n通常是输入序列长度。
|复杂度|算法|
|---|---|
|O(1)|swap(), iter_swap()|
|O(log(n))| lower_bound(), upper_bound(), equal_range(),binary_search(),push_heap(),pop_heap()|
|O(nlog(n))|inplace_merge()(最坏情况),stable_partition(最坏情况),sort(),stable_sort(),partial_sort(),partial_sort_copy(),sort_heap()|
|O(n
n)|find_end(),find_first_of(),search(), search_n()|
|O(n)|所有其他算法|

不修改序列的算法

  • for_each()
    f=for_each(b,e,f):对[b,e)中的每个x执行f(x)

  • 序列谓词
    all_of(b,e,f):[b,e)中所有元素x都满足f(x)吗?
    any_of(b,e,f):[b,e)中有元素x满足f(x)吗?
    none_of(b,e,f):[b,e)中所有元素x都不满足f(x)吗?

  • count()/count_if():寻找满足条件的元素数目

  • find/find_if/find_if_not/find_first_of/adjacent_find/find_end:顺序搜索具有特定值或令谓词为真的元素

  • equal/mismatch:比较一对序列

  • search/search_n:查找给定序列是否是另一个序列的子序列

修改序列的算法

修改序列的算法(可变序列算法,mutating sequence algorithm)可以修改其实参序列的元素。

  • transform
  • copy/copy_if/copy_n/copy_backward/move/move_backward:copy算法的目标序列不一定是一个容器,任何可用一个输出迭代器描述的东西都可以作为它的目标。
  • unique/unique_copy:从序列中删除连续的重复元素。
  • remove和replace
    • remove/remove_if/remove_copy./remove_copy_if:“删除”序列末尾元素
    • reverse/reverse_copy:逆序排列和拷贝
    • replace/replace_if/replace_copy/replace_copy_if
    • 这些算法不能改变输入序列的大小,即使是remove也会保持输入序列大小不变。类似unique,它是通过将元素移动到左侧来实现“删除”的。
  • rotate/retate_copy/random_shuffle/shuffle/partition/statble_partition/partition_copy/partition_point/is_partitioned:提供了移动序列中元素的系统方法,用swap来移动元素的。
  • 排列:提供了生成一方额序列所有排列的系统方法
    • next_permutation/prev_permutation/is_permutation
  • fill:fill系列算法提供了向序列元素赋值和初始化元素的方法
    • fill/fill_n/generate/generate_n/uninitialized_fill/uninitialized_fill_n/uninitialized_copy/uninitialized_copy_n
    • fill算法反复用指定值进行赋值,generate算法通过反复调用其函数实参得到的值进行赋值
    • 如果希望初始化,使用uinitialized_的版本。
  • swap/swap_ranges/iter_swap:交换对象的值,它是标准库中最简单也最重要的算法之一

排序和搜索

  • sort系列算法
    sort算法要求随机访问迭代器,排序list时一般将list元素拷贝到vector中,排序这个vector后再将元素拷回list。
    主要利用的是一种称为“快速排序”(QuickSort)的算法,虽然标准库的实现可能会依据不同的标准库实现而有所不同,但大体思路一致。C++标准库中的std::sort()函数通常是基于一种改进的快速排序算法,结合其他排序算法以提高性能。
  • binary_search:二分搜索算法提供了有序序列上的二分搜索,二分搜索算法不需要随机访问迭代器,一个前向迭代器就够了
    • lower_bound/upper_bound/binary_search/qual_range
  • merge:将两个有序序列合并为1个
    • merge/inplace_merge
  • 集合算法:集合算法将序列当作一个元素集合来处理,并提供基本的集合操作。输入数列应该是排好序的,输出序列也会被排序。
    • includes/set_union/set_intersection/set_difference/set_symmetric_difference
  • 堆:堆是一种按最大值优先的方式组织元素的紧凑型数据结构。可以理解为一种二叉树的表示方式。堆算法允许程序员将一个随机访问序列作为堆处理。
    • make_heap/push_heap/pop_heap/sort_heap/is_heap/is_heap_until
  • lexicographical_compare:字典序比较就是我们用来排序字典中单词的规则。

最大/最小值

  • min
  • max
  • minmax
  • min_element
  • max_element
  • minmax_element

仿函数

仿函数(Functor)是指可以像函数一样被调用的对象。在C++中,仿函数通过重载 operator() 操作符来实现。通过这一特性,仿函数可以保持状态(即成员变量),而普通函数不能保留状态。

仿函数的特性:

  1. 状态保持:仿函数可以有成员变量,因此可以保存状态。这在许多情况下比普通函数更灵活。
  2. 灵活性:可以通过构造函数传递参数进行初始化,然后在调用时使用这些参数。
  3. 可作为算法参数:可以方便地与STL算法结合使用,如 std::sortstd::for_each 等,作为排序规则、谓词等。

参考

STL源码剖析

posted @ 2025-02-21 17:08  main_c  阅读(1)  评论(0)    收藏  举报  来源