【C/C++】C++标准模板库:容器

本文概述了C++中的重要数据结构和容器,如vector、队列(queue,priority_queue,stack,deque)、集合(set,map,multiset,multimap)以及bitset,介绍了它们的基本操作和特性,如元素个数、空检查、插入/删除等。

引言

C++ STL 中提供了多种容器,它们在功能和性能上各有特点,适用于不同场景。主要的容器类型分为以下三大类:

1. 顺序容器

顺序容器按插入顺序存储元素,适合频繁的插入、删除和遍历操作。

  • std::vector (动态数组):是一个动态数组,在内存中以连续空间存储元素,支持快速随机访问。适用于需要动态扩展、支持随机访问,但不频繁在中间插入和删除的场景。

  • std::deque(双端队列):支持高效的头尾插入和删除操作,与 vector 类似,提供动态数组的功能,但不保证连续内存空间。适合需要在头尾频繁插入和删除的场景。

  • std::list(双向链表):采用双向链表实现,支持快速的插入和删除操作,但不支持随机访问。适合需要频繁在容器中间插入和删除的场景。

  • std::forward_list(单向链表):单链表,只能向前遍历,比 list 更节省空间。适合内存资源有限,且只需要单向遍历的场景。

  • std::array(静态数组):array 是 C++11 引入的一个定长数组包装类,大小在编译期确定,无法动态改变。它和普通数组类似,但提供了 STL 的接口。

2. 关联容器

关联容器内部使用平衡二叉树(通常为红黑树)存储元素,能够快速查找、插入和删除,适合对键进行排序和快速检索的场景。

  • std::set:存储唯一的键值元素,元素按升序排列,不支持重复元素。适合需要去重和有序访问的场景。

  • std::multiset:与 set 类似,但允许重复元素。

  • std::map:键值对容器,存储的元素是 <key, value> 形式,每个键是唯一的,元素按键值升序排列。适合按键快速查找和有序存储的场景。

  • std::multimap:与 map 类似,但允许重复键值。

3. 无序关联容器

无序关联容器在内部采用哈希表实现,元素没有顺序,但能提供快速的查找和插入性能(均摊复杂度为 O(1)),适合不需要有序存储的场景。

  • std::unordered_set:存储唯一的键值元素,元素无序,插入和查找的均摊时间复杂度较低。

  • std::unordered_multiset:与 unordered_set 类似,但允许重复元素。

  • std::unordered_map:键值对容器,键是唯一的,采用哈希表存储,元素无序。适合键值对查询性能要求较高的场景。

  • std::unordered_multimap:与 unordered_map 类似,但允许重复键值。

4. 容器适配器

容器适配器是对底层容器(如 dequelist)的一种封装,使其具有特定的行为。

  • std::stack:后进先出(LIFO)的栈结构,只能在栈顶插入和删除元素。通常由 deque 实现,也可以由 vectorlist 支持。

  • std::queue:先进先出(FIFO)的队列结构,只能在队列的头部和尾部插入和删除元素。默认使用 deque 作为底层容器。

  • std::priority_queue:优先级队列,元素按优先级排序,默认按降序排列(大顶堆)。可使用自定义比较器控制排序方式。

5. 容器总结对比

容器底层实现是否有序是否允许重复插入效率查找效率特点
vector动态数组无序允许O(1) 尾插O(1) 随机访问动态数组,支持快速随机访问和扩展
deque双端数组无序允许O(1) 头尾插O(1) 随机访问双端队列,支持头尾插入
list双向链表无序允许O(1) 任意位置插入O(n) 顺序查找双向链表,适合频繁插入和删除
forward_list单向链表无序允许O(1) 头部插入O(n) 顺序查找单向链表,节省空间
array静态数组无序允许O(1) 访问O(1) 访问定长数组,大小不可变
set红黑树有序不允许O(log n) 插入O(log n) 查找有序集合,不允许重复元素
multiset红黑树有序允许O(log n) 插入O(log n) 查找有序集合,允许重复元素
map红黑树有序不允许O(log n) 插入O(log n) 查找有序键值对,键唯一
multimap红黑树有序允许O(log n) 插入O(log n) 查找有序键值对,键可重复
unordered_set哈希表无序不允许O(1) 插入(均摊)O(1) 查找(均摊)无序集合,不允许重复元素
unordered_multiset哈希表无序允许O(1) 插入(均摊)O(1) 查找(均摊)无序集合,允许重复元素
unordered_map哈希表无序不允许O(1) 插入(均摊)O(1) 查找(均摊)无序键值对,键唯一
unordered_multimap哈希表无序允许O(1) 插入(均摊)O(1) 查找(均摊)无序键值对,键可重复
stack适配器(通常基于 deque--O(1) 压入/弹出-LIFO 栈,后进先出
queue适配器(通常基于 deque--O(1) 入队/出队-FIFO 队列,先进先出
priority_queue二叉堆--O(log n) 插入O(1) 取顶优先级队列,元素按优先级排序

C++ STL 提供了丰富的容器选择,可以根据实际场景选择合适的容器,以下是进一步的详细介绍。

一、vector

std::vector 是 C++ 标准模板库(STL)中最常用的容器之一,是一个动态数组,可以根据需要动态调整大小。与普通的数组相比,std::vector 提供了更多的灵活性和功能。以下是对 std::vector 的详细介绍:

1. 基本概念

std::vector 是一个模板类,定义在 <vector> 头文件中。允许存储和操作任意类型的元素,并在底层使用连续的内存布局。std::vector 可以看作是一个动态数组,因此可以在运行时动态扩展和缩小。

#include <vector>
std::vector<int> vec;

2. 主要特性

  • 自动扩展:当向 std::vector 添加新元素时,如果容量不足,它会自动分配更大的内存空间,并将旧数据复制到新空间中。
  • 连续存储std::vector 在内存中是连续存储的,类似于数组。这使得它与数组一样可以通过指针算术进行快速随机访问。
  • 动态调整大小std::vector 可以在运行时通过 resize()push_back()pop_back() 动态调整大小。
  • 内存管理std::vector 管理自己的内存,通过 capacity() 可以查看当前容量,通过 reserve() 可以预先分配空间,以减少后续的扩展开销。

3. 常用操作和方法

3.1 初始化

std::vector 可以通过多种方式初始化,如以下几种方式:

// 默认构造函数
std::vector<int> vec1;

// 指定大小的构造函数,所有元素初始化为默认值
std::vector<int> vec2(10);

// 指定大小和初始值
std::vector<int> vec3(10, 5);

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

// 使用拷贝构造函数
std::vector<int> vec5(vec4);

// 使用迭代器范围初始化
std::vector<int> vec6(vec4.begin(), vec4.end());

3.2 添加和删除元素

  • push_back(value):在末尾添加一个元素。
  • emplace_back(args...):在末尾原地构造一个元素(避免拷贝或移动操作)。
  • pop_back():删除末尾的元素。
  • insert(position, value):在指定位置插入一个元素。
  • erase(position):删除指定位置的元素。
  • clear():清空所有元素。

示例:

std::vector<int> vec = {1, 2, 3};
vec.push_back(4);       // vec: {1, 2, 3, 4}
vec.pop_back();         // vec: {1, 2, 3}
vec.insert(vec.begin(), 0); // vec: {0, 1, 2, 3}
vec.erase(vec.begin()); // vec: {1, 2, 3}
vec.clear();            // vec为空

3.3 访问元素

  • operator[]:直接通过下标访问元素,不进行边界检查。
  • at(index):通过下标访问元素,进行边界检查。
  • front():返回第一个元素。
  • back():返回最后一个元素。
  • data():返回指向数组首元素的指针,可以与C风格数组兼容。
std::vector<int> vec = {1, 2, 3};
int first = vec.front();   // 1
int last = vec.back();     // 3
int* ptr = vec.data();     // 指向vec的首元素的指针

3.4 大小和容量

  • size():返回当前元素个数。
  • capacity():返回分配的内存空间可以容纳的元素个数。
  • resize(new_size):调整容器大小,若增大,新元素用默认构造函数初始化。
  • reserve(new_capacity):调整容量,预先分配内存空间以减少后续扩展的开销。
  • shrink_to_fit():将多余的容量释放(不是强制要求,具体实现可能会忽略此请求)。
std::vector<int> vec(5);    // vec的size和capacity都是5
vec.reserve(10);            // vec的capacity变为10,size不变
vec.resize(3);              // vec的size变为3

二、deque

std::deque(双端队列)是 C++ 标准模板库(STL)中的一个容器,提供在两端高效插入和删除操作。它的内部实现通常使用多个固定大小的数组(块),允许灵活地在两端扩展。

1. 基本概念

std::deque 是一个模板类,定义在 <deque> 头文件中。与 std::vector 类似,std::deque 也支持动态大小,但与 vector 不同的是,deque 可以在头部和尾部都进行高效的插入和删除操作。

#include <deque>
std::deque<int> dq;

2. 主要特性

  • 双端插入和删除:支持在头部和尾部进行快速的插入和删除,时间复杂度为 O(1)。
  • 动态扩展std::deque 会根据需要动态调整大小,通常分配多个块来存储元素,以避免移动大量元素。
  • 随机访问:虽然访问效率不如 vector,但仍然支持通过下标访问元素,时间复杂度为 O(1)。
  • 不连续存储:内部使用分散的内存块,可能不保证所有元素在内存中的连续存储,这使得随机访问的局部性略差于 vector

3. 常用操作和方法

3.1 初始化

std::deque 可以通过多种方式初始化,如以下几种方式:

// 默认构造函数
std::deque<int> dq1;

// 指定大小的构造函数,所有元素初始化为默认值
std::deque<int> dq2(10);

// 指定大小和初始值
std::deque<int> dq3(10, 5);

// 使用列表初始化
std::deque<int> dq4 = {1, 2, 3, 4, 5};

// 使用拷贝构造函数
std::deque<int> dq5(dq4);

// 使用迭代器范围初始化
std::deque<int> dq6(dq4.begin(), dq4.end());

3.2 添加和删除元素

  • push_back(value):在尾部添加一个元素。
  • push_front(value):在头部添加一个元素。
  • pop_back():删除尾部的元素。
  • pop_front():删除头部的元素。
  • insert(position, value):在指定位置插入一个元素。
  • erase(position):删除指定位置的元素。
  • clear():清空所有元素。

示例:

std::deque<int> dq = {1, 2, 3};
dq.push_back(4);       // dq: {1, 2, 3, 4}
dq.push_front(0);      // dq: {0, 1, 2, 3, 4}
dq.pop_back();         // dq: {0, 1, 2, 3}
dq.pop_front();        // dq: {1, 2, 3}
dq.insert(dq.begin(), -1); // dq: {-1, 1, 2, 3}
dq.erase(dq.begin());  // dq: {1, 2, 3}
dq.clear();            // dq: 空

3.3 访问元素

  • operator[]:直接通过下标访问元素,不进行边界检查。
  • at(index):通过下标访问元素,进行边界检查。
  • front():返回头部元素。
  • back():返回尾部元素。
  • data():返回指向数组首元素的指针。
std::deque<int> dq = {1, 2, 3};
int first = dq.front();   // 1
int last = dq.back();     // 3
int* ptr = dq.data();     // 指向 dq 的首元素的指针

3.4 大小和容量

  • size():返回当前元素个数。
  • empty():检查是否为空。
  • resize(new_size):调整容器大小,若增大,新元素用默认构造函数初始化。
  • swap(deque):交换两个 deque 的内容。
std::deque<int> dq(5);    // dq 的 size 是 5
bool is_empty = dq.empty(); // false
dq.resize(3);              // dq 的 size 变为 3

总结

std::deque 是一个非常灵活的容器,适合需要频繁在头部和尾部插入和删除元素的场景。尽管它支持随机访问,但由于内部存储的分散性,访问效率可能稍逊于 std::vector

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值