《C++ STL容器入门:手把手教你使用常用容器》
适合人群:刚接触C++不久,还没系统用过STL容器,或者用过但总觉得混乱的初学者。
我们今天按「最常用 → 次常用 → 特殊场景」的顺序,把最核心的6个容器讲清楚:
| 容器类型 | 底层实现 | 是否有序 | 是否允许重复 | 随机访问 | 最典型用途 | 初学者记住的关键词 |
|---|---|---|---|---|---|---|
| vector | 动态数组 | 有序 | 允许 | O(1) | 「大多数情况下的默认选择」 | 连续、可扩容 |
| string | 字符动态数组 | 有序 | 允许 | O(1) | 字符串 | vector 的亲兄弟 |
| deque | 分段连续数组 | 有序 | 允许 | O(1) | 头尾频繁插入/删除 | 两端队列 |
| list | 双向链表 | 有序 | 允许 | O(n) | 中间频繁插入/删除 | 不连续、指针跳转 |
| set / multiset | 红黑树 | 有序 | set不允许 multiset允许 | 无 | 自动排序 + 去重 | 有序集合 |
| unordered_set | 哈希表 | 无序 | 不允许 | 无 | 快速判断“是否存在” | 哈希去重、O(1)查找 |
一、vector(99%新手最该先掌握的容器)
#include <vector>
#include <iostream>
using namespace std;
int main() {
// 1. 几种常见创建方式
vector<int> v1; // 空vector
vector<int> v2(10); // 10个0
vector<int> v3(10, 7); // 10个7
vector<int> v4 = {1, 2, 3, 4, 5}; // 初始化列表(最常用!)
// 2. 常用操作
v4.push_back(6); // 尾部添加 O(1) 均摊
v4.pop_back(); // 尾部删除 O(1)
cout << v4.size() << endl; // 当前元素个数
cout << v4.capacity() << endl; // 已分配空间(≥size)
// 3. 遍历(四种写法,推荐前两种)
// 写法1:范围for(最简洁,C++11+)
for(int x : v4) {
cout << x << " ";
}
cout << endl;
// 写法2:现代for + 下标(最常用)
for(size_t i = 0; i < v4.size(); ++i) {
cout << v4[i] << " ";
}
cout << endl;
// 写法3:迭代器(了解即可)
for(auto it = v4.begin(); it != v4.end(); ++it) {
cout << *it << " ";
}
// 4. 访问(越界是未定义行为!)
cout << v4.front() << endl; // 第一个元素
cout << v4.back() << endl; // 最后一个元素
cout << v4[2] << endl; // 下标访问(不检查边界)
cout << v4.at(2) << endl; // 边界检查版(越界抛异常)
// 5. 提前分配空间(非常重要!性能差距可达数倍)
vector<int> large;
large.reserve(1000000); // 提前申请100万空间,避免多次扩容
return 0;
}
vector使用铁律(背下来)
- 不知道用什么容器的时候 → 先用 vector
- 需要频繁在中间插入/删除 → 才考虑 list / deque
- 明确知道最终大小 → 构造时直接给大小 或 reserve
- 尽量用
v[i]而不是at(i)(性能更好,但自己保证不越界)
二、string(其实就是 vector 的特化版)
string s = "hello";
// 常用操作(几乎和 vector 一样)
s += " world"; // 拼接
s.push_back('!');
s.pop_back();
cout << s.size() << " " << s.length() << endl; // 两者等价
cout << s.empty() << endl;
// 查找
size_t pos = s.find("world"); // 返回下标,没找到返回 string::npos
if (pos != string::npos) {
cout << "找到了,位置:" << pos << endl;
}
// 替换、插入、删除
s.replace(0, 5, "Hi"); // 把前5个字符替换成 "Hi"
s.insert(2, "~~~"); // 在下标2处插入
s.erase(2, 3); // 从下标2开始删3个字符
// 子串
string sub = s.substr(0, 5); // 前5个字符
三、deque(双端队列)
#include <deque>
deque<int> dq;
dq.push_back(10); // 尾插
dq.push_front(5); // 头插
dq.pop_back(); // 尾删
dq.pop_front(); // 头删
// 支持随机访问(但比 vector 稍慢)
cout << dq[0] << " " << dq.at(1) << endl;
什么时候用 deque 而不是 vector?
- 需要频繁在头部插入/删除
- 想用队列,但又偶尔需要随机访问
四、list(双向链表)
#include <list>
list<int> lst = {1, 2, 3, 4, 5};
lst.push_front(0);
lst.push_back(6);
// 中间插入(已知位置时效率很高)
auto it = lst.begin();
++it; ++it; // 指向第三个元素(值为3)
lst.insert(it, 99); // 在3前面插入99
// 删除(已知迭代器时效率很高)
lst.erase(it); // 删除刚才的迭代器位置
list最大特点:
任意位置插入/删除都是 O(1)(只要你已经拿到迭代器),但不支持随机访问([] 运算符)。
五、set / multiset(自动排序 + 去重)
#include <set>
set<int> s;
s.insert(3);
s.insert(1);
s.insert(3); // 重复元素不会插入
s.insert(5);
s.insert(2);
// 遍历一定是升序
for(int x : s) cout << x << " "; // 输出 1 2 3 5
// 查找(O(log n))
if (s.count(3)) cout << "有3\n";
auto it = s.find(5);
if (it != s.end()) cout << *it << endl;
// 删除
s.erase(3); // 删除值为3的元素
multiset 允许重复元素,其余用法几乎一样。
六、unordered_set(哈希表,平均 O(1) 查找)
#include <unordered_set>
unordered_set<string> us;
us.insert("apple");
us.insert("banana");
us.insert("apple"); // 不会重复插入
// 判断是否存在(最快!)
if (us.count("apple")) {
cout << "有苹果\n";
}
// 遍历顺序是随机的(哈希桶顺序)
for(const auto& fruit : us) {
cout << fruit << " ";
}
unordered_set vs set 选择口诀
- 只关心“是否存在”,不在乎顺序 → unordered_set(更快)
- 需要自动排序、范围查询、lower_bound → set
- 元素是自定义类型 → 必须提供 hash 函数和 == 运算符(或用 unordered_set 时自己特化)
快速总结:初学者“一句话选容器”
| 需求 | 推荐容器 |
|---|---|
| 普通数组,想自动扩容 | vector |
| 字符串操作 | string |
| 需要频繁头尾插入/删除 | deque |
| 需要频繁中间插入/删除 | list |
| 需要自动去重 + 排序 | set |
| 只关心是否存在(最快判断) | unordered_set |
| 允许重复但要排序 | multiset |
最后建议的学习顺序(最有效率)
- vector + string(练 7 天)
- deque(了解即可)
- set / unordered_set(练判断存在、去重)
- list(真正用到再深入)
祝你早日把这六个容器用得顺手!
有哪个容器你现在最想看更详细的代码例子?
(比如 vector 扩容原理、unordered_set 自定义 hash、set 的 lower_bound 用法、list 的 splice 等)
直接告诉我,我马上给你针对性代码 + 说明~