C/C++ 入门:STL 容器

手动管理数组和字符串又麻烦又容易错,STL 提供了一组现成的容器和算法,让”存数据、查数据、排数据”变成几行代码的事。

📚 基本概念速读

名称 定义 省流
STL Standard Template Library,标准模板库 C++ 标准库的容器 + 算法
容器(container) 存储和管理一组元素的数据结构 装数据的东西
vector 动态数组,内存连续,自动扩容 自动扩容的数组
string 字符串类型,自动管理字符内存 好用的字符串
map 键值对映射,按键有序 字典/映射
set 不重复元素的有序集合 去重 + 有序
迭代器(iterator) 遍历容器的”指针式”对象 容器的通用游标
范围 for for (auto x : c) 遍历语法 简单遍历

🧩 STL 是什么

STL 是 C++ 标准库的核心组成部分,主要由三件东西协作:

组成 作用 例子
容器 存储数据 vectormapset
算法 操作数据 sortfind
迭代器 连接容器和算法 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> v;

// 尾部追加元素,自动扩容
v.push_back(10);
v.push_back(20);
v.push_back(30);

cout << v[0] << endl; // 10,按下标访问
cout << v.size() << endl; // 3,元素个数

return 0;
}

也可以在创建时直接给初始值:

1
2
vector<int> v = {1, 2, 3};     // 初始化列表
vector<int> v2(10, 0); // 10 个元素,都初始化为 0

遍历

1
2
3
4
5
6
7
8
9
// 方式一:按下标
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}

// 方式二:范围 for
for (int x : v) {
cout << x << " ";
}

size() 返回的是 size_t(无符号整数),所以循环变量建议也用 size_t,避免有符号/无符号比较的警告。

常用接口

接口 作用
push_back(x) 尾部追加元素
pop_back() 删除尾部元素
size() 元素个数
empty() 是否为空
front() / back() 第一个 / 最后一个元素
v[i] 按下标访问,不检查越界
v.at(i) 按下标访问,越界抛异常
1
2
v.push_back(40);   // 追加
v.pop_back(); // 删掉最后一个

v[i] 越界是未定义行为,可能悄悄读到脏数据;不确定下标是否合法时,用 v.at(i) 更安全。

📝 string:常用接口与互转

常用接口

string 是 C++ 的字符串类型,自动管理字符内存,可以拼接、比较、查找、截取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
using namespace std;

int main()
{
string s = "hello";

cout << s.length() << endl; // 5,长度
cout << s.size() << endl; // 5,和 length 一样

string t = s + " world"; // 拼接
cout << t << endl; // hello world

cout << (s == "hello") << endl; // 1,可以直接比较

size_t pos = t.find("world"); // 查找子串位置
cout << pos << endl; // 6

string sub = t.substr(6, 5); // 从位置 6 取 5 个字符
cout << sub << endl; // world

return 0;
}

find 找不到时返回 string::npos,判断要用它:

1
2
3
if (t.find("world") != string::npos) {
cout << "找到了" << endl;
}

与 C 字符串互转

1
2
3
4
5
6
7
string s = "hello";

// string -> C 字符串(const char*),用于传给旧接口
const char* c = s.c_str();

// C 字符串 -> string,直接构造即可
string t = c;

对比 C 风格的 char[]string 省去了手动管理长度、手动拼接的麻烦:

操作 C 风格 char[] string
拼接 strcat,要保证空间够 s1 + s2
比较 strcmp ==
长度 strlen .size()
拷贝 strcpy =

🗺️ map:键值对

基本用法

map 保存键值对(key-value),按键自动排序,查找、插入都很快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
map<string, int> scores;

// 插入:用 [] 或 insert
scores["Alice"] = 92;
scores["Bob"] = 88;
scores.insert({"Carol", 95});

// 查找:[] 会插入默认值,find 不会
auto it = scores.find("Bob");
if (it != scores.end()) {
cout << it->first << ": " << it->second << endl; // Bob: 88
}

// 遍历:按键升序
for (const auto& kv : scores) {
cout << kv.first << " -> " << kv.second << endl;
}

return 0;
}

输出(按键排序):

1
2
3
Alice -> 92
Bob -> 88
Carol -> 95

[]find 的区别

操作 键不存在时 适用场景
scores["Bob"] 自动插入一个默认值(0) 需要写入时
scores.find("Bob") 返回 end(),不插入 只查询时
1
int x = scores["Dave"];   // Dave 不存在,会被插入并初始化为 0

如果只是想查询某个键存不存在,用 findcount,避免误插入脏数据:

1
2
3
if (scores.count("Dave") == 0) {
cout << "没有 Dave" << endl;
}

🧹 set:去重与有序

set 保存不重复的元素,并且自动排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <set>
using namespace std;

int main()
{
set<int> s;

s.insert(3);
s.insert(1);
s.insert(3); // 重复,被忽略
s.insert(2);

cout << s.size() << endl; // 3,重复的 3 只算一个

// 遍历:自动升序
for (int x : s) {
cout << x << " "; // 1 2 3
}
cout << endl;

// 查找
if (s.count(2) > 0) {
cout << "2 在集合里" << endl;
}

return 0;
}

setvector 都能装数据,区别在语义:

特性 vector set
顺序 保持插入顺序 自动排序
重复 允许 不允许
按下标访问 可以 不可以,用迭代器

需要”去重 + 有序”时用 set;需要”保持插入顺序、按下标访问”时用 vector

🔁 迭代器:begin/end 与范围 for

基本概念

迭代器可以理解成”容器专用的指针”:begin() 指向第一个元素,end() 指向最后一个元素的下一个位置(不指向有效元素)。

1
2
3
4
5
6
vector<int> v = {10, 20, 30};

for (auto it = v.begin(); it != v.end(); it++) {
cout << *it << " "; // 解引用拿到元素
}
cout << endl;


flowchart LR
    A[begin 指向第一个元素] --> B[元素...]
    B --> C[end 指向末尾之后]

范围 for 就是迭代器遍历的语法糖,两者等价:

1
2
3
4
5
6
7
for (int x : v) { ... }

// 等价于
for (auto it = v.begin(); it != v.end(); it++) {
int x = *it;
...
}

mapset 也可以这样遍历。map 的迭代器解引用得到的是键值对,用 it->firstit->second 访问:

1
2
3
for (auto it = scores.begin(); it != scores.end(); it++) {
cout << it->first << ": " << it->second << endl;
}

迭代器失效问题

迭代器指向的是容器内部的数据。如果容器发生了会移动数据或释放内存的操作,旧迭代器就失效了,再用它访问是未定义行为。

最典型的例子是 vector 扩容:

1
2
3
4
5
6
7
vector<int> v = {1, 2, 3};

auto it = v.begin(); // 指向 1

v.push_back(4); // 可能触发扩容,数据搬到新内存

// cout << *it << endl; // 危险:it 可能已失效

什么时候要警惕:

操作 是否可能使旧迭代器失效
vector 扩容(push_back 等) 可能失效
vector 中间插入/删除 受影响位置之后失效
map/set 插入 通常不失效
遍历中删除当前元素 当前迭代器失效

范围 for 内部也是迭代器,所以在遍历时删除元素要格外小心,通常需要改用迭代器循环并小心处理,或先收集再删除。

🔀 sort 算法

默认排序

sort 属于 STL 算法,对迭代器范围内的元素排序,默认升序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
vector<int> v = {5, 2, 8, 1};

sort(v.begin(), v.end()); // 升序

for (int x : v) {
cout << x << " "; // 1 2 5 8
}
cout << endl;

return 0;
}

降序可以用标准库提供的比较器:

1
sort(v.begin(), v.end(), greater<int>());   // 降序

自定义比较函数

排自定义类型时,用 lambda 告诉 sort“按什么排”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <algorithm>
#include <string>
#include <vector>
using namespace std;

struct Student {
string name;
int score;
};

int main()
{
vector<Student> students = {
{"Alice", 92},
{"Bob", 88},
{"Carol", 95},
};

// 按分数从高到低排
sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});

for (const auto& s : students) {
cout << s.name << " " << s.score << endl;
}
return 0;
}

输出:

1
2
3
Carol 95
Alice 92
Bob 88

比较函数返回 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
setvector 都能去重 vector 不去重、保顺序;set 去重且有序
范围 for 遍历时随便删元素 删除元素可能导致迭代器失效,需小心处理
sort 只能排数字 传自定义比较函数就能排任意类型
STL 容器都要自己管理内存 容器自动管理内存,别手动 delete 容器内部数据

✅ 总结

STL 三件套各司其职:容器存数据(vector/string/map/set),迭代器统一遍历方式,算法直接复用(sort/find/count)。能用现成的,就别自己造。

学到这里,前面手动 new[]/delete[] 管理动态数组的章节,现在有了更省心的替代:vector 自动扩容、自动释放,配合 sort 一行排序。

Happy Hacking! 🎉