C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程

简介: C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法

引言

C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(std::vectorstd::arraystd::liststd::deque)、关联容器(std::setstd::map)和无序容器(std::unordered_setstd::unordered_map),全面解析它们的特点、用法、适用场景及常见操作。


一、C++ 容器的分类

C++ 容器按照用途大致分为三大类:

  1. 序列容器(Sequence Containers)

    • 元素按顺序存储。
    • 支持动态调整大小和顺序访问操作。
    • 包括:std::vectorstd::arraystd::dequestd::list
  2. 关联容器(Associative Containers)

    • 元素以键值对存储,通常用于高效查找。
    • 数据存储有序,底层实现为平衡二叉树(如红黑树)。
    • 包括:std::setstd::mapstd::multisetstd::multimap
  3. 无序容器(Unordered Containers)

    • 元素以哈希表存储,查找性能优于关联容器,但数据无序。
    • 包括:std::unordered_setstd::unordered_map

二、序列容器解析

1. std::vector

简介

std::vector 是一个动态数组,支持自动扩展和随机访问,适用于需要频繁随机访问的场景。它是初学者最常使用的容器之一,因为它的使用方式和普通数组非常类似,但多了动态管理内存的功能。

特点

  • 动态扩展std::vector 的大小会根据需求动态调整,当元素数目超过当前容量时,它会自动分配更多的内存来容纳新元素。

  • 连续存储:数据存储在连续的内存块中,因此访问性能高,遍历时效率优于链表等非连续存储容器。

  • 插入和删除效率

    • 尾部操作高效:在尾部添加或删除元素的时间复杂度是 O(1)O(1)O(1),非常高效。
    • 中间插入或删除效率低:由于 vector 是连续存储,插入或删除中间元素时,需移动大量元素,因此效率为 O(n)O(n)O(n)。

常用操作

操作 方法 描述
添加元素 push_back() 在尾部插入元素
删除尾部元素 pop_back() 删除尾部元素
插入元素 insert(iterator, value) 在指定位置插入元素
删除指定元素 erase(iterator) 删除指定位置的元素
随机访问元素 operator[]at() 随机访问指定索引处的元素
获取大小 size() 返回当前元素数量
检查是否为空 empty() 判断容器是否为空
调整大小 resize() 调整容器的大小,若新大小小于当前大小,超出部分的元素将被删除,若新大小大于当前大小,则会增加元素,默认初始化为零
清空容器 clear() 删除容器中的所有元素,容器的大小变为 0

示例代码

cpp复制代码#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3};

    // 添加元素
    vec.push_back(4);  // 在尾部添加元素 4

    // 修改元素
    vec[1] = 20;  // 修改第二个元素的值为 20

    // 插入和删除
    vec.insert(vec.begin() + 2, 10); // 在索引 2 的位置插入元素 10
    vec.erase(vec.begin() + 1);      // 删除索引 1 的元素

    // 调整大小
    vec.resize(5, 0); // 调整容器大小为 5,新增的元素初始化为 0

    // 清空容器
    vec.clear();      // 删除所有元素

    // 输出容器大小
    std::cout << "Vector size after clear: " << vec.size() << std::endl; // 输出 0

    return 0;
}

适用场景

std::vector 适合需要频繁随机访问或尾部增删元素的场景,比如处理一组动态变化的数值或管理待办事项列表等。resize() 方法可以用于需要改变容器大小的场景,尤其是当需要确保容器中有足够的元素,或者初始化额外的元素时。clear() 方法适用于需要快速清空容器内容的情况。


2. std::array

简介

std::array 是固定大小的静态数组,大小在编译时确定。它的用法与普通 C 风格数组非常相似,但提供了一些更安全、更便捷的操作接口。

特点

  • 轻量高效std::array 是静态分配的,因此不涉及动态内存分配,这使得它非常高效。
  • 固定大小:数组大小在编译时确定,因此不支持动态扩展,适合已知大小的数据集合。
  • 随机访问高效:访问任意元素的时间复杂度为 (O(1)),类似普通数组。

常用操作

操作 方法 描述
访问元素 operator[]at() 随机访问元素
获取大小 size() 返回固定大小
获取头尾元素 front() / back() 获取第一个或最后一个元素
填充所有元素 fill(value) 用指定值填充整个数组

示例代码

#include <array>
#include <iostream>

int main() {
   
    std::array<int, 4> arr = {
   1, 2, 3, 4};
    arr[2] = 10;  // 修改第三个元素的值为 10

    // 遍历并输出元素
    for (int val : arr) {
   
        std::cout << val << " ";
    } // 输出 1 2 10 4
    return 0;
}

适用场景

std::array 适合用于需要固定大小且已知的数据集合,比如存储 RGB 颜色值或者某个固定大小的矩阵行数据。


3. std::list

简介

std::list 是双向链表,适用于频繁的中间插入和删除操作。在链表中,每个元素都有一个指向前后元素的指针,这使得在任何位置进行插入和删除都非常高效。

特点

  • 高效插入和删除:在链表中的插入和删除时间复杂度为 (O(1)),不需要像 vector 一样移动其他元素。
  • 随机访问效率低:由于链表没有连续存储,不能通过索引直接访问某个元素,必须从头或尾遍历,因此随机访问的效率很低。

常用操作

操作 方法 描述
添加元素 push_front() / push_back() 在头部或尾部添加元素
删除元素 pop_front() / pop_back() 删除头部或尾部元素
插入元素 insert(iterator, value) 在指定位置插入元素
删除指定元素 erase(iterator) 删除指定位置的元素

示例代码

#include <list>
#include <iostream>

int main() {
   
    std::list<int> lst = {
   1, 2, 3};
    lst.push_back(4);  // 在尾部添加元素 4
    lst.insert(std::next(lst.begin(), 1), 10);  // 在第二个位置插入元素 10
    lst.pop_front();                            // 删除头部元素

    // 遍历并输出元素
    for (int val : lst) {
   
        std::cout << val << " ";
    } // 输出 10 2 3 4
    return 0;
}

适用场景

std::list 适合频繁的中间插入和删除,尤其是当数据集合较大并且需要灵活调整时,比如管理网络节点或实现复杂的缓存算法。


4. std::deque

简介

std::deque 是双端队列,支持在头部和尾部快速插入和删除。它可以理解为 vectorlist 的结合,具有两者的优点。

特点

  • 高效双端操作:无论是头部还是尾部插入/删除,时间复杂度均为 (O(1))。
  • 支持随机访问:与 vector 类似,deque 支持通过索引直接访问元素,但它的底层存储结构并非一个连续的内存块。

常用操作

操作 方法 描述
添加元素 push_front() / push_back() 在头部或尾部添加元素
删除元素 pop_front() / pop_back() 删除头部或尾部元素
随机访问元素 operator[]at() 随机访问元素

示例代码

#include <deque>
#include <iostream>

int main() {
   
    std::deque<int> dq = {
   1, 2, 3};
    dq.push_front(0);  // 在头部添加元素 0
    dq.push_back(4);   // 在尾部添加元素 4

    // 遍历并输出元素
    for (int val : dq) {
   
        std::cout << val << " ";
    } // 输出 0 1 2 3 4
    return 0;
}

适用场景

std::deque 适合在头尾都需要频繁增删数据的场景,比如模拟队列或双向缓存。


三、关联容器解析

关联容器用于存储键值对,通常用于高效查找、插入和删除操作。

1. std::set

简介

std::set 是集合容器,用于存储不重复的元素,底层实现为红黑树,具有自动排序的功能。

特点

  • 有序存储:元素按照升序排列,保证数据有序。
  • 查找高效set 使用平衡二叉树,查找、插入和删除的时间复杂度为 (O(\log n))。
  • 唯一性set 保证集合中不会有重复的元素。

常用操作

操作 方法 描述
添加元素 insert(value) 插入一个元素
删除元素 erase(iterator) 删除指定元素
查找元素 find(value) 查找元素是否存在

示例代码

#include <set>
#include <iostream>

int main() {
   
    std::set<int> s = {
   3, 1, 4};
    s.insert(2);  // 插入元素 2
    s.erase(1);   // 删除元素 1

    // 遍历并输出元素
    for (int val : s) {
   
        std::cout << val << " ";
    } // 输出 2 3 4
    return 0;
}

适用场景

std::set 适合需要保证数据唯一性并且需要有序存储的场景,比如保存用户 ID、追踪唯一的值等。


2. std::map

简介

std::map 是键值对容器,类似于字典,它也是通过红黑树实现的,因此提供了有序的数据存储方式。

特点

  • 有序存储:键值对按照键的顺序存储。
  • 查找高效map 的查找、插入和删除操作的时间复杂度为 (O(\log n))。
  • 键唯一:每个键都是唯一的。

常用操作

操作 方法 描述
添加元素 operator[]insert() 添加或更新键值对
删除元素 erase(iterator) 删除指定键的元素
查找元素 find(key) 查找指定键是否存在

示例代码

#include <map>
#include <iostream>

int main() {
   
    std::map<int, std::string> m = {
   {
   1, "one"}, {
   2, "two"}};
    m[3] = "three";  // 插入键值对 (3, "three")

    // 遍历并输出键值对
    for (auto& [key, value] : m) {
   
        std::cout << key << ": " << value << std::endl;
    }
    return 0;
}

适用场景

std::map 适合需要快速查找键值对的场景,比如存储用户信息或用于配置文件读取等。


总结:容器的选择指南

场景 推荐容器
随机访问 std::vector
数据大小固定且已知 std::array
频繁插入和删除 std::liststd::deque
有序存储和查找 std::setstd::map
无序存储和查找 std::unordered_set / std::unordered_map

通过掌握这些容器的特性和用法,你将能够在开发中游刃有余地选择最佳的容器,为程序带来性能和代码可读性的提升。对于刚开始学习 C++ 的萌新们,理解这些容器的基本特性和适用场景,是提高编程技能的重要一步!希望这篇文章对你理解和使用 C++ 容器有所帮助。
在这里插入图片描述

相关文章
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
1月前
|
监控 Kubernetes 安全
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
蒋星熠Jaxonic,技术探索者,以代码为笔,在二进制星河中书写极客诗篇。专注Docker与容器化实践,分享从入门到企业级应用的深度经验,助力开发者乘风破浪,驶向云原生新世界。
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
|
30天前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
262 2
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
204 2
|
8月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
418 73
|
5月前
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
61 0
|
8月前
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
8月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
323 3
|
9月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
476 1

热门文章

最新文章