C++ STL 容器:vector、list、map 的使用
C++ 标准模板库(STL)提供了三种最核心的容器:vector(动态数组)、list(双向链表)和 map(映射)。掌握它们的使用是编写高效 C++ 代码的基础。以下指南将直接展示如何在代码中应用它们,涵盖定义、增删改查及适用场景。
1. 使用 vector(动态数组)
vector 是一个能够自动扩展大小的数组。它的内存是连续的,支持通过下标快速访问,非常适合元素数量变化较大但主要进行尾部操作的场景。
1.1 准备工作
包含 <vector> 头文件以使用该容器。
#include <vector>
#include <iostream>
int main() {
// 代码示例将在此处编写
return 0;
}
1.2 创建与初始化
定义 一个存储整数类型的 vector。
std::vector<int> numbers; // 创建一个空 vector
std::vector<int> scores(10, 0); // 创建包含10个0的 vector
1.3 添加与访问元素
调用 push_back 方法在数组末尾添加元素。
numbers.push_back(5);
numbers.push_back(10);
numbers.push_back(15);
使用 下标运算符 [] 或 at() 方法读取元素。
int first = numbers[0]; // 获取第一个元素(5)
int second = numbers.at(1); // 获取第二个元素(10),会进行边界检查
遍历 容器打印所有元素。
for (size_t i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << " ";
}
// 输出: 5 10 15
1.4 删除与修改
修改 指定位置的元素。
numbers[1] = 20; // 将第二个元素修改为 20
调用 pop_back 移除最后一个元素。
numbers.pop_back(); // 移除 15
注意:vector 在中间插入或删除元素效率较低,因为需要移动后续所有元素。若需要在中间频繁操作,请考虑使用 list。
2. 使用 list(双向链表)
list 由节点组成,每个节点存储数据并指向前驱和后继。它在任何位置的插入和删除操作都非常快,时间复杂度为 $O(1)$,但不支持随机访问(不能直接用 numbers[2] 访问)。
2.1 准备工作
包含 <list> 头文件。
#include <list>
#include <iostream>
2.2 创建与初始化
定义 一个存储字符串的 list。
std::list<std::string> tasks;
2.3 添加元素
调用 push_back 或 push_front 在两端添加元素。
tasks.push_back("Coding");
tasks.push_front("Meeting");
tasks.push_back("Debugging");
此时链表顺序为:Meeting -> Coding -> Debugging。
2.4 插入与删除(核心优势)
获取 指向第二个元素的迭代器。
std::list<std::string>::iterator it = tasks.begin();
std::advance(it, 1); // 将迭代器向后移动一位,指向 "Coding"
调用 insert 在迭代器位置插入元素。
tasks.insert(it, "Refactoring");
// 顺序变为: Meeting -> Refactoring -> Coding -> Debugging
调用 erase 删除迭代器指向的元素。
tasks.erase(it); // 删除原迭代器指向的 "Coding"
// 顺序变为: Meeting -> Refactoring -> Debugging
2.5 遍历
由于不支持随机访问,使用 迭代器或范围 for 循环进行遍历。
for (const auto& task : tasks) {
std::cout << task << " -> ";
}
// 输出: Meeting -> Refactoring -> Debugging ->
3. 使用 map(键值对映射)
map 存储的是“键值对”。每个键(Key)都是唯一的,对应一个值(Value)。map 内部通常由红黑树实现,会根据键自动排序,查找效率很高,时间复杂度为 $O(\log n)$。
3.1 准备工作
包含 <map> 头文件。
#include <map>
#include <string>
3.2 创建与初始化
定义 一个键为 std::string,值为 int 的 map(例如存储学生分数)。
std::map<std::string, int> scores;
3.3 插入与修改
使用 下标运算符 [] 插入或修改数据。如果键不存在,会自动创建;如果存在,会覆盖旧值。
scores["Alice"] = 90;
scores["Bob"] = 85;
scores["Charlie"] = 95;
// 修改 Alice 的分数
scores["Alice"] = 88;
或者,调用 insert 方法插入。注意,如果键已存在,insert 不会修改现有值。
scores.insert(std::make_pair("David", 80));
3.4 查找与访问
使用 find 方法查找特定键是否存在。该方法返回一个迭代器。
auto search = scores.find("Bob");
if (search != scores.end()) {
std::cout << "Bob's score: " << search->second << std::endl;
} else {
std::cout << "Bob not found" << std::endl;
}
3.5 遍历
map 会按照键的字典序自动排序(A-Z)。
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 输出顺序可能是: Alice, Bob, Charlie, David
4. 容器选择对比表
在实际开发中,根据操作需求选择合适的容器至关重要。下表总结了三者的核心区别。
| 特性 | vector (动态数组) | list (双向链表) | map (映射) |
|---|---|---|---|
| 内存结构 | 连续内存 | 非连续内存(节点) | 红黑树结构(节点) |
| 随机访问 | 支持 ($O(1)$) | 不支持 | 不支持(仅支持通过键访问) |
| 头部/中间插入 | 慢 ($O(n)$) | 快 ($O(1)$) | 快 ($O(\log n)$) |
| 尾部插入 | 快 ($O(1)$,均摊) | 快 ($O(1)$) | N/A |
| 查找效率 | 慢(需遍历,除非有序) | 慢(需遍历) | 快 ($O(\log n)$) |
| 主要用途 | 存储元素个数可变,主要进行尾部操作 | 频繁在任意位置插入/删除 | 需要根据键快速查找值,或需要自动排序 |
总结选择逻辑:
- 需要通过下标(如
arr[i])快速访问?选择vector。 - 需要频繁在列表中间插入或删除数据,且很少随机访问?选择
list。 - 需要存储“键值对”关系,并需要通过键快速找到值?选择
map。
// 示例:综合使用
std::vector<int> dataStore; // 用于存储大量数据流水
std::list<int> activeTasks; // 用于管理需要动态移除的任务ID
std::map<int, std::string> idToName; // 用于ID与名字的快速对应查询
暂无评论,快来抢沙发吧!