STL 中的容器是用来存储和管理对象集合的类。容器根据其内部数据结构和访问方式可以分为三大类:顺序容器、关联容器和无序关联容器。下面是对这些容器的详细介绍
1. 顺序容器(Sequence Containers)
顺序容器用于按特定顺序存储元素。主要包括以下几种
1.1 *std::vector
• 特点:动态数组,支持快速随机访问,插入和删除操作在末尾很快,但在中间或前面插入和删除速度较慢(中间插入/删除用list)。
• 使用场景:需要动态调整大小且频繁进行随机访问(list/deque不支持这个功能)的场景。
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // 在末尾添加元素
for (int val : vec) {
std::cout << val << " ";
}
return 0;
}
1.2 std::deque
• 特点:双端队列,支持快速随机访问,允许在两端进行快速插入和删除操作。
• 使用场景:需要在两端插入和删除元素的场景。
#include <deque>
#include <iostream>
int main() {
std::deque<int> deq = {1, 2, 3, 4, 5};
deq.push_front(0); // 在前面添加元素
deq.push_back(6); // 在末尾添加元素
for (int val : deq) {
std::cout << val << " ";
}
return 0;
}
如定义一个task队列, 从队列的尾部添加新的task,从队列的头部开始执行task
template <class T>
class TaskQueue {
public:
TaskQueue() {}
virtual ~TaskQueue() {}
void AppendTask(T task) {
Lock lock(mutex_);
tasks_.push_back(task);
}
void TriggerAllTask() {
Lock lock(mutex_);
while (!tasks_.empty()) {
T task = tasks_.front();
tasks_.pop_front();
mutex_.Unlock();
task();
mutex_.Lock();
}
}
void pop_front() {
Lock lock(mutex_);
if (!tasks_.empty()) {
T task = tasks_.front();
tasks_.pop_front();
mutex_.Unlock();
task();
}
}
protected:
std::deque<T> tasks_;
PthreadMutex mutex_;
};
实例化上面的task如下:
class MsgTaskQueue
{
public:
static MsgTaskQueue& Instance() {
static MsgTaskQueue instance;
return instance;
}
void RemoveTask(const std::string uuId) {
std::lock_guard<std::mutex> lockGuard(mutex_);
for (std::deque<MsgTaskQueue>::iterator iter = tasks_.begin();
iter != tasks_.end();) {
if (iter->uuId == uuId) {
iter = tasks_.erase(iter);
} else {
++iter;
}
}
}
void AppendTask(MsgTaskQueuetask) {
std::lock_guard<std::mutex> lockGuard(mutex_);
tasks_.push_back(task);
}
void PopFront() {
std::lock_guard<std::mutex> lockGuard(mutex_);
if (!tasks_.empty()) {
MsgTaskQueue task = tasks_.front();
tasks_.pop_front();
mutex_.unlock();
task();
}
}
protected:
std::deque<MsgTaskQueue> tasks_;
std::mutex mutex_;
};
1.3 ** std::list
• 特点:双向链表,支持快速插入和删除操作,但不支持随机访问。
• 使用场景:需要频繁在中间插入和删除元素的场景。
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
lst.push_back(6); // 在末尾添加元素
lst.push_front(0); // 在前面添加元素
for (int val : lst) {
std::cout << val << " ";
}
return 0;
}
1.4 std::forward_list
特点:单向链表,只能向前遍历,内存开销比 std::list 小。
• 使用场景:需要轻量级的链表结构且只需向前遍历的场景。
1.5 std::array
特点:固定大小的数组,大小在编译时确定
使用场景:需要固定大小数组且不需要动态调整大小的场景。
#include <array>
#include <iostream>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
for (int val : arr) {
std::cout << val << " ";
}
return 0;
}
1.6 std::vector<bool>
• 特点:特殊化的 std::vector,用于存储布尔值。
• 注意:由于实现方式的不同,std::vector<bool> 不能像其他 std::vector 那样直接访问元素
#include <vector>
#include <iostream>
int main() {
std::vector<bool> vec = {true, false, true};
for (bool val : vec) {
std::cout << val << " ";
}
return 0;
}
2.关联容器(Associative Containers)
关联容器用于以键值对的形式存储元素,并基于键进行快速查找
2.1 std::set
• 特点:存储唯一的键,自动排序。
• 使用场景:需要存储唯一元素并快速查找的场景。
#include <set>
#include <iostream>
int main() {
std::cout << " main started" << std::endl;
std::set<int> s = {1, 2, 7, 4, 5};
for (int val : s) {
std::cout << val << " ";
}
return 0;
}
output:
main started
1 2 4 5 7
2.2 std::map
• 特点:存储键值对,键唯一且自动排序。
• 使用场景:需要根据键快速查找值的场景。
#include <map>
#include <iostream>
int main() {
std::map<int, std::string> m = {{1, "one"}, {7, "two"}, {3, "three"}};
for (const auto& [key, val] : m) {
std::cout << key << ": " << val << std::endl;
}
return 0;
}
1: one
3: three
7: two
2.3 std::multiset
• 特点:存储可重复的键,自动排序。
• 使用场景:需要存储重复元素并快速查找的场景。
2.4 std::multimap
• 特点:存储键值对,键可重复且自动排序。
• 使用场景:需要根据键快速查找多个值的场景。
3. 无序关联容器(Unordered Associative Containers)
无序关联容器通过哈希表实现,支持更快的平均查找速度
3.1 std::unordered_set
3.2 std::unordered_map
3.3 std::unordered_multiset
3.4 std::unordered_multimap
4.迭代器的使用
4.1 访问迭代器中的值
* 解引用
->
4.2 基本用法(注意:删除其中的某个数据)
4.2.1 遍历
#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;
// 使用范围 for 循环(C++11 引入)
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
4.2.2 修改(访问其中的数据)
vector/list:
使用* 或者->
#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) {
*it *= 2; // 将每个元素乘以 2
}
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
map:
使用iter->second;
4.2.2.1 注意删除操作
1. 从 std::vector 删除元素
std::vector 的删除操作会导致后续元素移动,从而使迭代器失效。解决办法是使用 erase 方法的返回值来更新迭代器
#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(); /* 空 */) {
if (*it % 2 == 0) {
it = vec.erase(it); // 更新迭代器
} else {
++it; // 仅在没有删除时递增迭代器
}
}
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
2. 从 std::list 删除元素
std::list 的删除操作不会使其他迭代器失效,但仍需要更新当前迭代器。
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
for (std::list<int>::iterator it = lst.begin(); it != lst.end(); /* 空 */) {
if (*it % 2 == 0) {
it = lst.erase(it); // 更新迭代器
} else {
++it; // 仅在没有删除时递增迭代器
}
}
for (int val : lst) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
3. 从 std::map 或 std::unordered_map 删除元素
std::map 和 std::unordered_map 的删除操作也不会使其他迭代器失效,但需要更新当前迭代器。
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}};
for (auto it = myMap.begin(); it != myMap.end(); /* 空 */) {
if (it->first % 2 == 0) {
it = myMap.erase(it); // 更新迭代器
} else {
++it; // 仅在没有删除时递增迭代器
}
}
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << " ";
}
std::cout << std::endl;
return 0;
}
4.2.2.2 遍历过程插入数据
对于vector/list
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 在遍历过程中插入数据
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 3) {
it = vec.insert(it, 99); // 在元素3之前插入99
++it; // 移动迭代器到下一个位置
}
}
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
5.map与unordered_map(红黑树VS哈希表)
适用场景
1. 需要有序或范围查询的访问:例如需要按键排序输出或范围查询。
2. 查找、插入和删除操作频繁:但对性能要求不是极限
假设需要管理一个学生成绩的系统,并且需要按成绩排序输出:
不需要有序访问:例如快速查找某个键值对。
对性能要求较高:需要快速插入、删除和查找操作。
假设需要管理一个产品库存系统,并且需要快速查找某个产品的库存数量: