STL相关总结

STL(Standard Template Library,标准模板库)是 C++ 标准库中的一个重要组成部分。STL 提供了一组通用的模板类和算法,极大地简化了数据结构和算法的使用。STL 的设计思想是基于泛型编程,允许用户在不牺牲效率的情况下,使用高度抽象的通用数据结构和算法。

1. STL 的核心组件

STL 由以下三个主要部分组成:

  • 容器(Containers):存储和管理数据的集合。
  • 迭代器(Iterators):遍历容器元素的工具,充当容器与算法之间的桥梁。
  • 算法(Algorithms):对容器中的数据进行操作的函数集。

1.1 容器(Containers)

容器是 STL 中用于存储数据的模板类。不同的容器适用于不同的应用场景,根据数据的存储和访问方式,STL 容器可以分为三类:

序列式容器(Sequence Containers)
序列式容器用于存储有序的数据,这些容器中的元素按插入顺序进行存储。

  • std::vector:动态数组,提供随机访问能力,可以动态调整大小。
  • std::deque:双端队列,支持在两端快速插入和删除元素。
  • std::list:双向链表,支持在任意位置快速插入和删除元素,但不支持随机访问。
  • std::forward_list:单向链表,类似于 std::list,但只能向前遍历。

关联式容器(Associative Containers)
关联式容器用于存储有序的键值对集合,支持高效的查找、插入和删除操作。

  • std::set:有序集合,存储唯一的元素,支持高效的查找。
  • std::multiset:类似于 std::set,但允许重复的元素。
  • std::map:有序字典,以键值对的形式存储元素,键唯一。
  • std::multimap:类似于 std::map,但允许重复的键。

无序关联式容器(Unordered Associative Containers)
无序关联式容器类似于关联式容器,但它们的元素不是按顺序存储的,而是使用哈希表存储的,提供更快的平均时间复杂度。

  • std::unordered_set:无序集合,存储唯一的元素。
  • std::unordered_multiset:类似于 std::unordered_set,但允许重复的元素。
  • std::unordered_map:无序字典,以键值对的形式存储元素,键唯一。
  • std::unordered_multimap:类似于 std::unordered_map,但允许重复的键。

容器适配器(Container Adapters)
容器适配器提供了对基本容器的一层包装,使其行为更像特定的数据结构。

  • std::stack:栈,后进先出(LIFO)的数据结构,基于 std::deque 实现。
  • std::queue:队列,先进先出(FIFO)的数据结构,基于 std::deque 实现。
  • std::priority_queue:优先级队列,允许访问最高优先级的元素,基于堆实现。

1.2 迭代器(Iterators)

迭代器是 STL 中用于遍历容器元素的工具,它们为容器与算法之间提供了统一的接口。迭代器的设计使得算法可以独立于具体的容器操作。

迭代器的种类

  • 输入迭代器(Input Iterator):只读访问,支持从容器中读取数据,通常用于单遍扫描。
  • 输出迭代器(Output Iterator):只写访问,支持向容器中写入数据。
  • 前向迭代器(Forward Iterator):可以多次遍历,同样支持只读访问。
  • 双向迭代器(Bidirectional Iterator):支持在容器中双向遍历,如 std::list 和 std::set。
  • 随机访问迭代器(Random Access Iterator):支持在容器中进行任意位置的访问,如 std::vector 和 std::deque。

迭代器的使用
迭代器的使用非常类似于指针,支持通过 * 操作符访问元素,++ 操作符移动到下一个元素。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

1.3 算法(Algorithms)

STL 提供了一组通用算法,用于操作容器中的元素。算法是 STL 的核心组件之一,可以执行诸如搜索、排序、修改和排序等操作。

常用算法

  • 非变异算法:不修改容器内容,只读取数据。例如 std::find, std::count, std::accumulate。
  • 变异算法:修改容器内容。例如 std::copy, std::replace, std::fill。
  • 排序算法:对容器元素进行排序。例如 std::sort, std::partial_sort, std::nth_element。
  • 数值算法:执行数值计算。例如 std::accumulate, std::inner_product, std::adjacent_difference。

算法的使用
算法通常以容器的迭代器为参数,从而对容器中的元素进行操作。下面是一个简单的例子,使用 std::sort 对 std::vector 进行排序:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2};
    
    std::sort(vec.begin(), vec.end());

    for (const auto& val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

1.4 泛型编程

STL 是泛型编程的经典应用,它使得算法与容器分离,能够独立于特定的数据结构而工作。通过使用模板,STL 实现了通用的数据结构和算法,它们可以适用于不同类型的容器和数据类型。

模板函数
STL 中的算法大多是模板函数,可以用于任何符合迭代器接口的容器类型。例如,std::sort 既可以用于 std::vector,也可以用于 std::deque。

模板类
STL 中的容器和迭代器都是模板类。模板类的设计使得它们可以存储和操作任何类型的元素。例如,std::vector 是一个存储 int 类型元素的向量,而 std::vectorstd::string 是一个存储字符串的向量。

1.5 STL 的高级特性

容器与算法的互操作性
由于迭代器提供了统一的接口,STL 中的所有算法都可以与任何容器类型配合使用。这种互操作性是 STL 强大的原因之一。

函数对象与 Lambda 表达式
STL 中的算法通常接受函数对象(function object)或 Lambda 表达式作为参数,用于指定自定义的比较规则或操作。例如:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2};
    
    std::sort(vec.begin(), vec.end(), [](int a, int b) {
        return a > b;  // 自定义比较规则:降序排序
    });

    for (const auto& val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

迭代器适配器
STL 还提供了迭代器适配器(iterator adapters),如 std::back_inserter、std::front_inserter 和 std::reverse_iterator,用于扩展迭代器的功能。例如,std::back_inserter 可以将元素插入到容器的末尾。

2. 容器使用

容器(Containers)是 C++ 标准模板库(STL)的核心组件之一,用于存储和管理数据。C++ 提供了多种类型的容器,每种容器都有其特定的用途和特性。下面我将详细讲解 C++ 中常用的几种容器及其使用方法。

2.1 序列式容器(Sequence Containers)

序列式容器是用于存储有序集合的容器,它们按照插入顺序存储元素。

2.1.1 std::vector

std::vector 是一个动态数组,允许你在数组的末尾添加或删除元素。std::vector 支持快速随机访问,但在中间插入或删除元素的开销较大。

基本操作

#include <iostream>
#include <vector>

int main() {
    // 创建一个空的 vector
    std::vector<int> vec;

    // 添加元素
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    // 访问元素
    std::cout << "Element at index 1: " << vec[1] << std::endl;

    // 使用迭代器遍历元素
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 删除元素
    vec.pop_back();  // 删除最后一个元素

    // 获取大小和容量
    std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;

    return 0;
}

常用操作

  • push_back():在向量的末尾添加元素。
  • pop_back():删除向量的最后一个元素。
  • size():返回向量中的元素个数。
  • capacity():返回向量的容量,即在不重新分配内存的情况下,向量能够存储的元素个数。
  • resize():改变向量的大小。
  • reserve():为向量预留一定的内存空间。

push_back()、pop_back()、size()、capacity()、resize() 和 reserve() 是 std::vector 中常用的成员函数,它们各自的功能和用途有所不同。下面详细解释这些函数的作用及其区别:

  1. push_back()

功能:push_back() 用于在 std::vector 的末尾添加一个新元素。每次调用 push_back() 时,向量的大小(即元素个数)都会增加 1。

扩展能力:如果 vector 的当前容量(capacity)不足以容纳新元素,push_back() 会自动扩展容量。扩展容量时,vector 会重新分配内存,并将现有元素复制到新内存块中。

示例:

std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
// vec 现在包含两个元素:{1, 2}
  1. pop_back()

功能:pop_back() 删除 std::vector 的最后一个元素。每次调用 pop_back() 时,向量的大小(size)会减少 1。

注意:pop_back() 不返回被删除的元素,也不减少向量的容量(capacity),只是减少了向量的大小。

示例:

std::vector<int> vec = {1, 2, 3};
vec.pop_back();
// vec 现在包含两个元素:{1, 2}
  1. size()

功能:size() 返回 std::vector 中当前存储的元素个数,也就是向量的大小。

与容量的区别:size() 返回的是实际存储在向量中的元素数量,而不是向量可以存储的总容量(那是 capacity() 的作用)。

示例:

std::vector<int> vec = {1, 2, 3};
std::cout << "Size: " << vec.size() << std::endl;  // 输出: Size: 3
  1. capacity()

功能:capacity() 返回 std::vector 当前的容量,即在不需要重新分配内存的情况下,向量最多可以存储的元素数量。

扩展与缩减:capacity() 可能大于或等于 size()。当 vector 需要增加元素且当前容量不足时,capacity() 会自动扩展。当 capacity() 扩展时,通常会按倍数增加。

示例:

std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
std::cout << "Capacity: " << vec.capacity() << std::endl;  // 输出: Capacity: 2(或者更大,取决于实现)
  1. resize()

功能:resize() 改变 std::vector 的大小(size)。它可以增加或减少向量的大小。

扩大向量:如果新大小大于当前大小,resize() 会在末尾插入值初始化的元素(默认值为 T() 或者你指定的值)。
缩小向量:如果新大小小于当前大小,resize() 会删除多余的元素。
对容量的影响:resize() 可能会改变 capacity(),但不总是如此。如果新大小超过当前容量,则 vector 会重新分配内存并扩展容量。

示例:

std::vector<int> vec = {1, 2, 3};
vec.resize(5);
// vec 现在包含:{1, 2, 3, 0, 0}

vec.resize(2);
// vec 现在包含:{1, 2}
  1. reserve()

功能:reserve() 为 std::vector 预留至少指定数量的内存空间,但不会改变向量的大小(size)。

与 resize() 的区别:reserve() 只影响容量(capacity),不影响大小(size)。它仅预分配内存,以避免后续添加大量元素时频繁的内存重新分配(提高性能)。

用途:reserve() 适用于知道将要添加多少元素的情况下,通过预先分配足够的空间来优化性能。

示例:

std::vector<int> vec;
vec.reserve(10);
std::cout << "Capacity after reserve: " << vec.capacity() << std::endl;  // 输出: Capacity after reserve: 10

总结与区别

  • push_back():在向量的末尾添加元素,如果容量不足则自动扩展容量。
  • pop_back():删除向量的最后一个元素,不影响容量。
  • size():返回向量中的元素个数(实际存储的数量)。
  • capacity():返回向量的容量,即在不重新分配内存的情况下,向量能够存储的最大元素数量。
  • resize():改变向量的大小,可能会增加或减少向量的元素数量,也可能影响容量。
  • reserve():为向量预留一定的内存空间,不改变向量的大小,只影响容量。

2.1.2 std::deque

std::deque 是双端队列,支持在两端快速插入和删除元素。std::deque 提供了几乎所有 std::vector 的功能,此外还支持在前端快速插入和删除元素。

基本操作

#include <iostream>
#include <deque>

int main() {
    // 创建一个 deque
    std::deque<int> deq;

    // 在前端和末尾添加元素
    deq.push_front(1);
    deq.push_back(2);
    deq.push_front(3);

    // 访问元素
    std::cout << "First element: " << deq.front() << std::endl;
    std::cout << "Last element: " << deq.back() << std::endl;

    // 删除元素
    deq.pop_front();  // 删除第一个元素
    deq.pop_back();   // 删除最后一个元素

    // 使用迭代器遍历元素
    for (std::deque<int>::iterator it = deq.begin(); it != deq.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

常用操作

  • push_front() 和 push_back():在 deque 的前端或末尾添加元素。
  • pop_front() 和 pop_back():删除 deque 的第一个或最后一个元素。
  • front() 和 back():访问 deque 的第一个和最后一个元素。

2.1.3 std::list 和 std::forward_list

std::list 是双向链表,允许在容器中的任意位置快速插入和删除元素,但不支持随机访问。std::forward_list 是单向链表,比 std::list 更节省内存,但只能向前遍历。

基本操作

#include <iostream>
#include <list>

int main() {
    // 创建一个 list
    std::list<int> lst = {1, 2, 3};

    // 在任意位置插入元素
    lst.insert(++lst.begin(), 4);  // 在第二个位置插入 4

    // 访问第一个和最后一个元素
    std::cout << "First element: " << lst.front() << std::endl;
    std::cout << "Last element: " << lst.back() << std::endl;

    // 删除元素
    lst.pop_front();  // 删除第一个元素

    // 使用迭代器遍历元素
    for (std::list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

常用操作

  • push_front() 和 push_back():在 list 的前端或末尾添加元素。
  • pop_front() 和 pop_back():删除 list 的第一个或最后一个元素。
  • insert() 和 erase():在 list 的任意位置插入或删除元素。

2.2 关联式容器(Associative Containers)

关联式容器用于存储键值对,通常是有序的,支持高效的查找、插入和删除操作。

2.2.1 std::set 和 std::multiset

std::set 是一个有序集合,存储唯一的元素。std::multiset 允许重复元素。

基本操作

#include <iostream>
#include <set>

int main() {
    // 创建一个 set
    std::set<int> s = {3, 1, 4, 1, 5};

    // 插入元素
    s.insert(2);

    // 查找元素
    auto it = s.find(3);
    if (it != s.end()) {
        std::cout << "Found 3" << std::endl;
    }

    // 遍历元素
    for (auto& elem : s) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    return 0;
}

常用操作

  • insert():向 set 中插入元素,set 会自动保持元素的有序性。
  • find():查找某个元素是否存在于 set 中。
  • erase():删除 set 中的某个元素。

2.2.2 std::map 和 std::multimap

std::map 是一个有序字典,以键值对的形式存储元素,键唯一。std::multimap 允许多个相同的键。

基本操作

#include <iostream>
#include <map>

int main() {
    // 创建一个 map
    std::map<int, std::string> m;

    // 插入键值对
    m[1] = "one";
    m[2] = "two";
    m[3] = "three";

    // 访问元素
    std::cout << "Key 1: " << m[1] << std::endl;

    // 遍历键值对
    for (const auto& pair : m) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

常用操作

  • operator[]:通过键访问或插入元素。
  • insert():插入键值对,map 会自动保持键的有序性。
  • find():查找某个键是否存在于 map 中。
  • erase():删除 map 中的某个键值对。

2.3 无序关联式容器(Unordered Associative Containers)

无序关联式容器使用哈希表实现,元素的存储顺序与插入顺序无关,但查找和插入操作的平均时间复杂度较低。

2.3.1 std::unordered_set 和 std::unordered_multiset

std::unordered_set 是一个无序集合,存储唯一的元素。std::unordered_multiset 允许重复元素。

基本操作

#include <iostream>
#include <unordered_set>

int main() {
    // 创建一个 unordered_set
    std::unordered_set<int> uset = {3, 1, 4, 1, 5};

    // 插入元素
    uset.insert(2);

    // 查找元素
    auto it = uset.find(3);
    if (it != uset.end()) {
        std::cout << "Found 3" << std::endl;
    }

    // 遍历元素
    for (auto& elem : uset) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    return 0;
}

常用操作

  • insert():向 unordered_set 中插入元素,unordered_set 使用哈希表存储元素。
  • find():查找某个元素是否存在于 unordered_set 中。
  • erase():删除 unordered_set 中的某个元素。

2.3.2 std::unordered_map 和 std::unordered_multimap

std::unordered_map 是一个无序字典,以键值对的形式存储元素,键唯一。std::unordered_multimap 允许多个相同的键。

基本操作

#include <iostream>
#include <unordered_map>

int main() {
    // 创建一个 unordered_map
    std::unordered_map<int, std::string> umap;

    // 插入键值对
    umap[1] = "one";
    umap[2] = "two";
    umap[3] = "three";

    // 访问元素
    std::cout << "Key 1: " << umap[1] << std::endl;

    // 遍历键值对
    for (const auto& pair : umap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

常用操作

  • operator[]:通过键访问或插入元素。
  • insert():插入键值对,unordered_map 使用哈希表存储键值对。
  • find():查找某个键是否存在于 unordered_map 中。
  • erase():删除 unordered_map 中的某个键值对。

2.4 容器适配器(Container Adapters)

容器适配器为基础容器提供了特定的接口,使其行为类似于其他数据结构。

2.4.1 std::stack

std::stack 是后进先出(LIFO)的数据结构,通常基于 std::deque 实现。

基本操作

#include <iostream>
#include <stack>

int main() {
    // 创建一个 stack
    std::stack<int> s;

    // 压入元素
    s.push(1);
    s.push(2);
    s.push(3);

    // 访问栈顶元素
    std::cout << "Top element: " << s.top() << std::endl;

    // 弹出元素
    s.pop();

    std::cout << "New top element: " << s.top() << std::endl;

    return 0;
}

常用操作

  • push():将元素压入栈顶。
  • pop():移除栈顶元素。
  • top():访问栈顶元素。

2.4.2 std::queue 和 std::priority_queue

std::queue 是先进先出(FIFO)的数据结构,通常基于 std::deque 实现。std::priority_queue 是一个优先级队列,元素按照优先级排序,通常基于堆实现。

基本操作

#include <iostream>
#include <queue>

int main() {
    // 创建一个 queue
    std::queue<int> q;

    // 压入元素
    q.push(1);
    q.push(2);
    q.push(3);

    // 访问队头元素
    std::cout << "Front element: " << q.front() << std::endl;

    // 弹出元素
    q.pop();

    std::cout << "New front element: " << q.front() << std::endl;

    return 0;
}

常用操作

  • push():将元素压入队尾。
  • pop():移除队头元素。
  • front() 和 back():访问队头和队尾元素。

2.5 选择合适的容器

选择合适的容器取决于应用场景的需求。以下是一些选择的建议:

  • 随机访问:如果需要快速随机访问元素,使用 std::vector 或 std::deque。
  • 频繁插入/删除:如果需要频繁在中间插入或删除元素,使用 std::list 或 std::deque。
  • 唯一性与排序:如果需要存储唯一的元素,并且需要保持有序,使用 std::set 或 std::map。
  • 哈希查找:如果需要快速查找和插入操作,使用 std::unordered_set 或 std::unordered_map。
  • 栈/队列:如果需要后进先出或先进先出的数据结构,使用 std::stack 或 std::queue。
  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值