C/C++ 入门:STL 容器
手动管理数组和字符串又麻烦又容易错,STL 提供了一组现成的容器和算法,让”存数据、查数据、排数据”变成几行代码的事。
📚 基本概念速读
| 名称 | 定义 | 省流 |
|---|---|---|
| STL | Standard Template Library,标准模板库 | C++ 标准库的容器 + 算法 |
| 容器(container) | 存储和管理一组元素的数据结构 | 装数据的东西 |
vector |
动态数组,内存连续,自动扩容 | 自动扩容的数组 |
string |
字符串类型,自动管理字符内存 | 好用的字符串 |
map |
键值对映射,按键有序 | 字典/映射 |
set |
不重复元素的有序集合 | 去重 + 有序 |
| 迭代器(iterator) | 遍历容器的”指针式”对象 | 容器的通用游标 |
| 范围 for | for (auto x : c) 遍历语法 |
简单遍历 |
🧩 STL 是什么
STL 是 C++ 标准库的核心组成部分,主要由三件东西协作:
| 组成 | 作用 | 例子 |
|---|---|---|
| 容器 | 存储数据 | vector、map、set |
| 算法 | 操作数据 | sort、find |
| 迭代器 | 连接容器和算法 | begin()、end() |
STL
用模板(template)实现,所以容器能装任意类型:vector<int>、vector<string>、map<string, int>。尖括号里的类型就是模板参数。
flowchart LR
A[容器] <--> B[迭代器]
B <--> C[算法]
A --> D[vector/string/map/set]
C --> E[sort/find/count]
先记住一个总原则:能用 STL 容器解决的,不要自己造轮子。
🧮 vector:动态数组
基本用法
vector
是动态数组:像数组一样按下标访问,又能自动扩容,不用自己管内存。
1 |
|
也可以在创建时直接给初始值:
1 | vector<int> v = {1, 2, 3}; // 初始化列表 |
遍历
1 | // 方式一:按下标 |
size()返回的是size_t(无符号整数),所以循环变量建议也用size_t,避免有符号/无符号比较的警告。
常用接口
| 接口 | 作用 |
|---|---|
push_back(x) |
尾部追加元素 |
pop_back() |
删除尾部元素 |
size() |
元素个数 |
empty() |
是否为空 |
front() / back() |
第一个 / 最后一个元素 |
v[i] |
按下标访问,不检查越界 |
v.at(i) |
按下标访问,越界抛异常 |
1 | v.push_back(40); // 追加 |
v[i]越界是未定义行为,可能悄悄读到脏数据;不确定下标是否合法时,用v.at(i)更安全。
📝 string:常用接口与互转
常用接口
string 是 C++
的字符串类型,自动管理字符内存,可以拼接、比较、查找、截取。
1 |
|
find 找不到时返回
string::npos,判断要用它:
1 | if (t.find("world") != string::npos) { |
与 C 字符串互转
1 | string s = "hello"; |
对比 C 风格的 char[],string
省去了手动管理长度、手动拼接的麻烦:
| 操作 | C 风格 char[] |
string |
|---|---|---|
| 拼接 | strcat,要保证空间够 |
s1 + s2 |
| 比较 | strcmp |
== |
| 长度 | strlen |
.size() |
| 拷贝 | strcpy |
= |
🗺️ map:键值对
基本用法
map
保存键值对(key-value),按键自动排序,查找、插入都很快。
1 |
|
输出(按键排序):
1 | Alice -> 92 |
[] 和 find 的区别
| 操作 | 键不存在时 | 适用场景 |
|---|---|---|
scores["Bob"] |
自动插入一个默认值(0) | 需要写入时 |
scores.find("Bob") |
返回 end(),不插入 |
只查询时 |
1 | int x = scores["Dave"]; // Dave 不存在,会被插入并初始化为 0 |
如果只是想查询某个键存不存在,用 find 或
count,避免误插入脏数据:
1 | if (scores.count("Dave") == 0) { |
🧹 set:去重与有序
set 保存不重复的元素,并且自动排序。
1 |
|
set 和 vector 都能装数据,区别在语义:
| 特性 | vector |
set |
|---|---|---|
| 顺序 | 保持插入顺序 | 自动排序 |
| 重复 | 允许 | 不允许 |
| 按下标访问 | 可以 | 不可以,用迭代器 |
需要”去重 + 有序”时用
set;需要”保持插入顺序、按下标访问”时用vector。
🔁 迭代器:begin/end 与范围 for
基本概念
迭代器可以理解成”容器专用的指针”:begin()
指向第一个元素,end()
指向最后一个元素的下一个位置(不指向有效元素)。
1 | vector<int> v = {10, 20, 30}; |
flowchart LR
A[begin 指向第一个元素] --> B[元素...]
B --> C[end 指向末尾之后]
范围 for 就是迭代器遍历的语法糖,两者等价:
1 | for (int x : v) { ... } |
map、set 也可以这样遍历。map
的迭代器解引用得到的是键值对,用 it->first 和
it->second 访问:
1 | for (auto it = scores.begin(); it != scores.end(); it++) { |
迭代器失效问题
迭代器指向的是容器内部的数据。如果容器发生了会移动数据或释放内存的操作,旧迭代器就失效了,再用它访问是未定义行为。
最典型的例子是 vector 扩容:
1 | vector<int> v = {1, 2, 3}; |
什么时候要警惕:
| 操作 | 是否可能使旧迭代器失效 |
|---|---|
vector 扩容(push_back 等) |
可能失效 |
vector 中间插入/删除 |
受影响位置之后失效 |
map/set 插入 |
通常不失效 |
| 遍历中删除当前元素 | 当前迭代器失效 |
范围 for 内部也是迭代器,所以在遍历时删除元素要格外小心,通常需要改用迭代器循环并小心处理,或先收集再删除。
🔀 sort 算法
默认排序
sort 属于 STL
算法,对迭代器范围内的元素排序,默认升序。
1 |
|
降序可以用标准库提供的比较器:
1 | sort(v.begin(), v.end(), greater<int>()); // 降序 |
自定义比较函数
排自定义类型时,用 lambda 告诉 sort“按什么排”:
1 |
|
输出:
1 | Carol 95 |
比较函数返回 true 表示”a 应该排在 b
前面”。注意比较函数要满足严格弱序:相等时返回 false,不要写
>= 这种包含等号的条件。
小结
| 用法 | 代码 |
|---|---|
| 升序 | sort(v.begin(), v.end()) |
| 降序 | sort(v.begin(), v.end(), greater<int>()) |
| 按自定义规则 | sort(v.begin(), v.end(), lambda) |
⚠️ 常见误区
| 误区 | 正解 |
|---|---|
vector 下标越界会报错 |
v[i] 不检查越界,是未定义行为;用 v.at(i)
会抛异常 |
string 就是 char[] |
string 自动管理内存,支持
+、==、find 等操作 |
用 map[] 查询很安全 |
键不存在时 [] 会插入默认值;只查询用
find/count |
set 和 vector 都能去重 |
vector 不去重、保顺序;set 去重且有序 |
| 范围 for 遍历时随便删元素 | 删除元素可能导致迭代器失效,需小心处理 |
sort 只能排数字 |
传自定义比较函数就能排任意类型 |
| STL 容器都要自己管理内存 | 容器自动管理内存,别手动 delete 容器内部数据 |
✅ 总结
STL 三件套各司其职:容器存数据(
vector/string/map/set),迭代器统一遍历方式,算法直接复用(sort/find/count)。能用现成的,就别自己造。
学到这里,前面手动 new[]/delete[]
管理动态数组的章节,现在有了更省心的替代:vector
自动扩容、自动释放,配合 sort 一行排序。
Happy Hacking! 🎉