当前位置: 首页 > article >正文

C++ STL:深入探索常见容器

你好呀,欢迎来到 Dong雨 的技术小栈 🌱

在这里,我们一同探索代码的奥秘,感受技术的魅力 ✨。

👉 我的小世界:Dong雨

📌 分享我的学习旅程
🛠️ 提供贴心的实用工具
💡 记录每一个灵感火花

描述

🌟✨ Hello,探索技术的你,这里是本篇的地图指南! ✨🌟

C++ STL:深入探索常见容器

引言:高效编程的核心

C++ 标准模板库(STL)是构建高效程序的强大工具,它提供了多种容器,帮助开发者应对复杂的数据存储和操作需求。STL 容器支持广泛的操作,包括动态内存管理、灵活的元素存储、以及高效的访问与查找能力。本文将深入探讨 STL 中常见的容器,并分析它们的特性、优势和适用场景。


1. vector:动态扩展的数组

概述:

vector 是 STL 中最常用的容器之一,它实现了一个动态数组。vector 具有自动扩容的特性,能够根据实际需求调整内存大小。这使得它特别适合处理需要频繁插入和删除元素的场景,尤其是当元素数量不确定时。

使用场景:

vector 适用于需要频繁访问元素、存储大量数据、并且数据规模动态变化的应用场景,如:

  • 存储大量数字数据或对象。
  • 图像处理中的像素数据。
  • 快速随机访问的应用。

头文件:

#include <vector>
using namespace std;

初始化:

vector<int> vec = {1, 2, 3, 4};  // 使用列表初始化

常用方法:

  • push_back(value):将元素添加到 vector 的末尾。
  • pop_back():移除 vector 末尾的元素。
  • at(index):获取指定位置的元素,提供越界检查。
  • size():返回 vector 中元素的数量。
  • reserve(n):预先为 vector 分配内存,以避免在频繁添加元素时导致多次扩容。

深入分析:

vector 提供了 O(1) 时间复杂度的随机访问,且支持 动态扩容。当 vector 的容量不足以容纳新元素时,它会自动扩容,通常扩展为原来的两倍,这样就能确保每次扩容时的操作成本较低(均摊复杂度为 O(1))。然而,vector 的扩容也有其开销,尤其是当容器中的元素非常大时,内存重新分配和元素拷贝可能会影响性能。

示例代码:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> numbers = {1, 2, 3, 4};  // 初始化 vector

    numbers.push_back(5);  // 添加元素

    // 输出 vector 中的所有元素
    cout << "Vector contents: ";
    for (const auto& num : numbers) {
        cout << num << " ";
    }
    cout << endl;

    cout << "Vector size: " << numbers.size() << endl;  // 获取大小

    return 0;
}

输出:

Vector contents: 1 2 3 4 5
Vector size: 5

2. stack:后进先出(LIFO)容器

概述:

stack 是遵循后进先出(LIFO)原则的容器。stack 只允许从顶端进行插入和删除操作,因此特别适合处理递归问题和需要回溯的任务。

使用场景:

stack 常用于以下场景:

  • 深度优先搜索(DFS)算法。
  • 撤销操作。
  • 表达式求值(如括号匹配问题)。

头文件:

#include <stack>
using namespace std;

初始化:

stack<int> s;

常用方法:

  • push(value):将元素压入栈顶。
  • pop():移除栈顶元素。
  • top():访问栈顶元素。
  • empty():检查栈是否为空。
  • size():返回栈中元素的数量。

深入分析:

stack 底层通常使用 dequevector 来实现,因此它的时间复杂度是 O(1),无论是入栈还是出栈。stack 只提供对栈顶元素的访问,不支持中间元素的访问。适合用来处理先进后出的操作,但若需要频繁查找其他元素时,应考虑其他数据结构。

示例代码:

#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;

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

    // 输出栈顶元素
    cout << "Stack top: " << s.top() << endl;

    // 弹出栈顶元素
    s.pop();
    cout << "New stack top: " << s.top() << endl;

    return 0;
}

输出:

Stack top: 3
New stack top: 2

3. queue:先进先出(FIFO)队列

概述:

queue 是遵循先进先出(FIFO)规则的容器,常用于需要按顺序处理任务的场景。

使用场景:

queue 常用于:

  • 事件驱动程序。
  • 任务调度(如操作系统中的进程调度)。
  • 异步消息传递。

头文件:

#include <queue>
using namespace std;

初始化:

queue<int> q;

常用方法:

  • push(value):将元素加入队列。
  • pop():移除队列头部元素。
  • front():访问队列头部元素。
  • back():访问队列尾部元素。
  • empty():检查队列是否为空。
  • size():返回队列中元素的数量。

深入分析:

queue 是基于 双端队列deque)实现的,它的操作复杂度为 O(1)。队列的操作仅限于队首和队尾,因此它在需要保持顺序处理任务的场景中非常高效。但在需要从队列中间插入或删除元素时,队列并不是一个好的选择。

示例代码:

#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<int> q;

    // 入队
    q.push(10);
    q.push(20);
    q.push(30);

    // 输出队列头部元素
    cout << "Queue front: " << q.front() << endl;

    // 出队
    q.pop();
    cout << "New queue front: " << q.front() << endl;

    return 0;
}

输出:

Queue front: 10
New queue front: 20

4. deque:双端队列

概述:

deque 是一个可以从两端插入和删除元素的容器。与 vector 相比,deque 更适合需要从两端频繁操作元素的场景。

使用场景:

deque 常用于:

  • 滑动窗口问题。
  • 双向任务队列。
  • 游戏中的消息队列。

头文件:

#include <deque>
using namespace std;

初始化:

deque<int> dq = {1, 2, 3, 4};

常用方法:

  • push_front(value):将元素添加到队列前端。
  • push_back(value):将元素添加到队列末端。
  • pop_front():移除队列前端的元素。
  • pop_back():移除队列末端的元素。
  • front():访问队列前端元素。
  • back():访问队列末端元素。
  • size():返回队列中元素的数量。

深入分析:

deque 允许在队列两端进行插入和删除操作,因此它比 vector 更适合双向操作。其底层通常是由多个块构成的结构,这使得它的空间效率较高,但相较于 vector,它的访问元素速度稍慢,尤其是对尾端元素的访问。因此,deque 在某些双端任务中表现优越,但若只在一端操作,vector 可能更高效。

示例代码:

#include  #include  using namespace std;

int main() { deque dq = {1, 2, 3, 4};
// 添加元素到队头
dq.push_front(0);
// 添加元素到队尾
dq.push_back(5);

// 输出队列元素
cout << "Deque contents: ";
for (const auto& val : dq) {
    cout << val << " ";
}
cout << endl;

return 0;
}

输出:

Deque contents: 0 1 2 3 4 5

5. priority_queue:优先级队列

概述:

priority_queue 是一个根据优先级排序的队列,默认情况下使用最大堆实现。它允许快速访问最大或最小元素,但不像常规队列一样按顺序处理元素。priority_queue 保证每次出队的元素都是当前队列中的最大(或最小)元素。

使用场景:

priority_queue 常用于:

  • 排序问题。
  • 实现 Dijkstra 算法、A* 算法等图搜索算法。
  • 实现事件驱动系统中的优先级任务调度。

头文件:

#include <queue>
using namespace std;

初始化:

priority_queue<int> pq;

常用方法:

  • push(value):将元素加入优先级队列。
  • pop():移除队列中的最大元素。
  • top():访问优先级队列中的最大元素。
  • empty():检查队列是否为空。
  • size():返回队列中元素的数量。

深入分析:

priority_queue 的底层通常使用最大堆或最小堆(通过自定义比较器来改变堆的顺序)。由于堆的性质,pushpop 操作的时间复杂度为 O(log n)。虽然它非常适合用于按优先级处理元素,但它不支持访问中间元素或随机访问,因此如果需要按特定条件查找元素,priority_queue 可能不是最佳选择。

示例代码:

#include <iostream>
#include <queue>
using namespace std;

int main() {
    priority_queue<int> pq;

    // 向优先级队列中添加元素
    pq.push(10);
    pq.push(20);
    pq.push(15);

    // 输出优先级队列中的最大元素
    cout << "Top element: " << pq.top() << endl;

    // 移除最大元素
    pq.pop();
    cout << "New top element: " << pq.top() << endl;

    return 0;
}

输出:

Top element: 20
New top element: 15

6. map:键值对存储(红黑树)

概述:

map 是一种关联容器,用于存储键值对。map 中的每个元素都由一个唯一的键和一个与之关联的值组成,且元素按键的顺序自动排序。map 默认使用 红黑树 实现,保证了插入、查找、删除操作的时间复杂度为 O(log n)

使用场景:

map 常用于:

  • 需要根据键高效查找值的场景。
  • 实现频率计数、符号表等数据结构。
  • 数据按键排序的场景。

头文件:

#include <map>
using namespace std;

初始化:

map<int, string> m;

常用方法:

  • insert({key, value}):向 map 插入键值对。
  • erase(key):删除指定键的元素。
  • find(key):查找指定键的元素。
  • at(key):访问指定键的值,若键不存在抛出异常。
  • size():返回 map 中的元素数量。

深入分析:

map 是一个基于红黑树实现的排序容器,因此所有的键值对都保持按键排序的状态。这使得 map 在需要排序数据或进行键查找时非常高效。然而,由于其底层结构的特性,map 中的元素不能像 unordered_map 那样以 O(1) 时间复杂度进行查找。

示例代码:

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> m;

    // 向 map 中插入元素
    m.insert({1, "Apple"});
    m.insert({2, "Banana"});
    m[3] = "Cherry";

    // 输出所有键值对
    for (const auto& p : m) {
        cout << p.first << ": " << p.second << endl;
    }

    return 0;
}

输出:

1: Apple
2: Banana
3: Cherry

7. set:无重复的有序集合

概述:

set 是一个集合类型的容器,存储的是唯一元素,并且按照升序(或自定义顺序)排列。set 的实现通常基于 红黑树,所以它的插入、删除和查找操作都具有 O(log n) 的时间复杂度。

使用场景:

set 适用于:

  • 需要无重复元素的场景。
  • 元素按特定顺序进行排序的需求。
  • 需要高效查找、插入和删除的场景。

头文件:

#include <set>
using namespace std;

初始化:

set<int> s;

常用方法:

  • insert(value):将元素插入集合。
  • erase(value):删除指定的元素。
  • find(value):查找元素。
  • size():返回集合中元素的数量。
  • empty():检查集合是否为空。

深入分析:

set 内部使用红黑树保持元素有序,支持自动排序,因此其插入、查找、删除操作的时间复杂度为 O(log n)。但是,set 不允许重复元素,因此在插入元素时,如果元素已经存在,它不会再次插入。

示例代码:

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s;

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

    // 输出所有元素
    cout << "Set contents: ";
    for (const auto& elem : s) {
        cout << elem << " ";
    }
    cout << endl;

    return 0;
}

输出:

Set contents: 1 2 3

8. pair:存储两个元素的组合

概述:

pair 是一个简单的容器,用于存储两个相关联的元素。pair 是一个模板类,可以用来存储任何两种类型的值。

使用场景:

pair 常用于:

  • 关联两个不同类型的数据。
  • 作为返回值,尤其是函数需要返回多个相关的值时。
  • mapset 一起使用来存储键值对。

头文件:

#include <utility>
using namespace std;

初始化:

pair<int, string> p = {1, "Apple"};

常用方法:

  • first:访问 pair 的第一个元素。
  • second:访问 pair 的第二个元素。

深入分析:

pair 提供了一个简单而有效的方式来将两个不同类型的数据捆绑在一起。它的访问非常方便,直接通过 firstsecond 成员进行访问。pair 在处理关联数据(如键值对)时非常有用。

示例代码:

#include <iostream>
#include <utility>
using namespace std;

int main() {
    pair<int, string> p = {1, "Apple"};

    // 输出 pair 中的元素
    cout << "First: " << p.first << ", Second: " << p.second << endl;

    return 0;
}

输出:

First: 1, Second: Apple

9. string:动态字符数组

概述:

string 是 C++ 标准库中用于处理文本数据的类。它支持动态调整大小,能够灵活处理字符串的插入、删除、查找和替换等操作。

使用场景:

string 常用于:

  • 处理动态长度的文本数据。
  • 文件操作、网络协议解析、用户输入等。

头文件:

#include <string>
using namespace std;

初始化:

string s = "Hello, world!";

常用方法:

  • length():返回字符串的长度。
  • empty():检查字符串是否为空。
  • append(str):向字符串末尾追加内容。
  • find(substr):查找子字符串的位置。
  • substr(start, length):返回子字符串。

深入分析:

string 类是一个非常强大且灵活的容器,提供了大量处理文本数据的函数。string 内部使用动态数组,因此能够自动扩展其容量。字符串操作通常涉及到字符的复制、移动或插入,所以操作时需要特别注意效率和内存管理。

示例代码:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "Hello, world!";
    
    // 查找字符串中的字符位置
    cout << "Found 'world' at position: " << s.find("world") << endl;

    // 取子字符串
    string sub = s.substr(7, 5);
    cout << "Sub-string: " << sub << endl;

    return 0;
}

输出:

Found 'world' at position: 7
Sub-string: world

10. bitset:固定大小的位集合

概述:

bitset 是一个用于处理固定数量位的容器,提供对位的操作。bitset 可以用来进行二进制操作、实现位掩码等应用。它的大小在编译时就已确定,因此非常高效。

使用场景:

bitset 常用于:

  • 位掩码和位操作。
  • 存储和操作二进制状态数据。
  • 实现某些低级算法(如布尔运算)。

头文件:

#include <bitset>
using namespace std;

初始化:

bitset<8> b1;  // 默认值为 00000000
bitset<8> b2("10101010");  // 初始化为二进制字符串

常用方法:

  • set(pos):将指定位置的位设为 1。
  • reset(pos):将指定位置的位设为 0。
  • flip(pos):翻转指定位置的位。
  • size():返回 bitset 的大小。
  • count():返回 1 的位数。

深入分析:

bitset 是一个非常高效的位操作工具。它使用固定大小的数组来存储位,并提供直接的位操作方法。由于其大小在编译时就已确定,因此对于存储较小二进制数据非常合适。不过,bitset 不适合动态大小的位集合,且无法像 vector 那样随时扩展。

示例代码:

#include <iostream>
#include <bitset>
using namespace std;

int main() {
    bitset<8> b1("10101010");

    // 输出 bitset 的内容
    cout << "Initial bitset: " << b1 << endl;

    // 翻转第 2 位
    b1.flip(2);
    cout << "After flipping 2nd bit: " << b1 << endl;

    // 获取 1 的位数
    cout << "Number of 1s: " << b1.count() << endl;

    return 0;
}

输出:

Initial bitset: 10101010
After flipping 2nd bit: 10100010
Number of 1s: 3

11. array:固定大小的数组

概述:

array 是 C++11 引入的一个容器,它是一个固定大小的数组容器。与 C 风格数组不同,array 是一个模板类,它支持 STL 容器的所有常用操作(如迭代器、排序等),但大小在编译时就已经确定。

使用场景:

array 适用于:

  • 需要固定大小数组的场景。
  • 需要类似于传统数组的性能,且希望获得更强大的 STL 操作支持时。

头文件:

#include <array>
using namespace std;

初始化:

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

常用方法:

  • at(index):返回指定位置的元素。
  • size():返回数组的大小。
  • fill(value):将所有元素初始化为相同的值。
  • front():返回第一个元素。
  • back():返回最后一个元素。

深入分析:

array 使得 C 风格数组拥有了 STL 容器的一些优势。它的大小固定,因此可以在栈上高效分配。相比于 vectorarray 不支持动态扩展,但它提供了更强大的 STL 接口和更高的类型安全性。适合存储大小固定的数据。

示例代码:

#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 5> arr = {1, 2, 3, 4, 5};

    // 输出数组的元素
    cout << "Array elements: ";
    for (const auto& elem : arr) {
        cout << elem << " ";
    }
    cout << endl;

    // 修改第一个元素
    arr[0] = 10;
    cout << "After modifying first element: " << arr[0] << endl;

    return 0;
}

输出:

Array elements: 1 2 3 4 5 
After modifying first element: 10

12. tuple:异构容器

概述:

tuple 是一个可以存储多个不同类型元素的容器。与 pair 类似,tuple 可以包含多个不同类型的元素,并且支持访问每个元素。tuple 提供了一种灵活的方式来捆绑多种类型的数据。

使用场景:

tuple 适用于:

  • 返回多个不同类型的值的函数。
  • 存储多个不同类型的参数或数据。
  • 元素数量在编译时已知的场景。

头文件:

#include <tuple>
using namespace std;

初始化:

tuple<int, string, float> t = {1, "Apple", 3.14};

常用方法:

  • get<index>(tuple):访问指定索引的元素。
  • tuple_size<tuple>::value:返回 tuple 的大小。
  • make_tuple():创建一个 tuple

深入分析:

tuple 是 C++ 中的一个非常灵活的容器,能够存储不同类型的数据。虽然它不如 vectormap 那样高频使用,但在需要处理多种类型的函数返回值或数据组合时非常有用。tuple 支持通过索引和类型访问元素。

示例代码:

#include <iostream>
#include <tuple>
using namespace std;

int main() {
    tuple<int, string, float> t = {1, "Apple", 3.14};

    // 访问 tuple 中的元素
    cout << "First: " << get<0>(t) << ", Second: " << get<1>(t) << ", Third: " << get<2>(t) << endl;

    // 修改 tuple 中的元素
    get<1>(t) = "Banana";
    cout << "Modified tuple: " << get<0>(t) << ", " << get<1>(t) << ", " << get<2>(t) << endl;

    return 0;
}

输出:

First: 1, Second: Apple, Third: 3.14
Modified tuple: 1, Banana, 3.14

总结:

在这篇博客中,我们深入分析了 C++ STL 中的常用容器。每个容器都有其特定的使用场景和优缺点。在实际开发中,选择合适的容器是编写高效和易于维护代码的关键。了解每个容器的工作原理、常用方法以及深入分析将帮助你在复杂的应用中做出更好的选择。

通过对以下容器的详细讲解和示例,你可以清晰地了解每个容器的特性和应用场景:

  1. vector:动态数组,适用于需要快速随机访问的场景。
  2. stack:栈,适用于后进先出(LIFO)的场景。
  3. queue:队列,适用于先进先出(FIFO)的场景。
  4. deque:双端队列,适用于两端操作都频繁的场景。
  5. priority_queue:优先级队列,适用于按优先级排序的场景。
  6. map:键值对容器,适用于根据键查找值的场景。
  7. set:无重复元素的有序集合,适用于去重和排序的场景。
  8. pair:存储两个相关元素的组合,常用于 map 中。
  9. string:动态字符串,适用于文本处理的场景。
  10. bitset:固定大小的位集合,适用于位运算的场景。
  11. array:固定大小的数组,适用于大小已知且不变的场景。
  12. tuple:存储多个不同类型元素的容器,适用于函数返回多个不同类型值的场景。

每个容器的底层实现和时间复杂度有所不同,选择正确的容器能显著提高程序的性能和可维护性。希望这篇文章能帮助你更好地理解 C++ STL 容器的使用和选择。

🎉🌈 陪伴至此,感谢有你 🌈🎉

感谢你能坚持看到这里!如果这篇文章对你有一点点帮助,希望能收获你的:
👍 一个赞,⭐ 一个收藏,💬 一条评论 或 🔗 一键分享!
你的支持是我持续输出的最大动力!✨

有问题?有灵感?
别犹豫,直接留言和我交流~让我们一起成长、一起突破 💡。

最后,祝你:

🍯 生活美满如蜜香
🌞 心情灿烂似朝阳
🌱 成长如树渐成章
🚀 未来闪耀梦飞翔!

再次感谢你的阅读!🌟 下次再见~ 🎉

感谢

http://www.kler.cn/a/521888.html

相关文章:

  • KNN算法学习实践
  • Tensor 基本操作2 理解 tensor.max 操作,沿着给定的 dim 是什么意思 | PyTorch 深度学习实战
  • 新年祝词(原创)
  • Linux - 进程间通信(2)
  • RAG技术:通过向量检索增强模型理解与生成能力
  • 装饰SpringMVC的适配器实现响应自动包装
  • springboot 动态配置定时任务
  • LabVIEW 查找COM数量和名称
  • 开发环境搭建-4:WSL 配置 docker 运行环境
  • 【回溯+剪枝】回溯算法的概念 全排列问题
  • 动态规划DP 数字三角形模型 传纸条(题目分析+C++完整代码)
  • 提示词设计流程 ——《如何从0开始构建一个基于强化学习的AI智能体》使用场景为例
  • 机试题——最小矩阵宽度
  • 互联网概述
  • 【开源免费】基于Vue和SpringBoot的美食推荐商城(附论文)
  • 云计算的概念与特点:开启数字化时代的新篇章
  • 链表排序--(奇数位是升序,偶数位是降序)
  • 算法-遍历分发糖果
  • 解码大数据的四个V:体积、速度、种类与真实性
  • SpringMVC的参数处理
  • c语言中mysql_query的概念和使用案例
  • Niagara学习笔记
  • 解决Oracle SQL语句性能问题(10.5)——常用Hint及语法(7)(其他Hint)
  • 【linux】Linux 常见目录特性、权限和功能
  • LockSupport概述、阻塞方法park、唤醒方法unpark(thread)、解决的痛点、带来的面试题
  • Firewalld 防火墙