C++ STL 编程:容器、算法与函数对象的奇妙冒险
🎒 STL 是 C++ 程序员的"瑞士军刀"——别人还在手写排序、手动管理链表的时候,你已经用一行代码搞定了。本文从 STL 编程思想出发,带你玩转容器、算法和函数对象,让你的代码从"能跑"进化到"优雅"。
📖 文章导航
- 9.1 STL 编程思想:STL 是什么?为什么它能让代码变优雅?
- 9.2 STL 容器:顺序容器(vector、list、deque)+ 关联容器(set、map)
- 9.3 STL 算法:排序、查找、遍历、变换……几十种算法任你挑
- 9.4 STL 函数对象:让函数也能当"参数"传来传去
9.1 STL 编程思想——把"造轮子"变成"拼乐高"
1.1 什么是 STL?
STL(Standard Template Library,标准模板库)是 C++ 自带的一个超级工具箱。它提供了三大件:
STL 三大支柱
├── 容器(Container) ← 存数据的"盒子"
├── 算法(Algorithm) ← 处理数据的"机器"
└── 迭代器(Iterator) ← 连接容器和算法的"桥梁"
想象你去宜家买家具:
- 容器就是你买回来的收纳柜(不同形状、不同大小)
- 算法就是你的组装工具(螺丝刀、扳手、锤子)
- 迭代器就是你的手——把手伸进柜子里取东西
1.2 STL 的核心思想:泛型编程
STL 的精髓在于模板(template)——算法不关心你存的是 int、double 还是 string,它只关心"你这个容器支持什么操作"。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 存 int
vector<int> nums = {5, 2, 8, 1, 9};
sort(nums.begin(), nums.end());
for (int n : nums) cout << n << " "; // 1 2 5 8 9
cout << endl;
// 存 string——同一个 sort 函数,换个容器就能用!
vector<string> names = {"Charlie", "Alice", "Bob"};
sort(names.begin(), names.end());
for (auto& s : names) cout << s << " "; // Alice Bob Charlie
cout << endl;
return 0;
}
💡 一句话理解 STL:写一次算法,能用在任何类型的容器上——这就是"泛型编程"的魅力。
9.2 STL 容器——你的数据住哪种"房子"?
STL 容器分为两大门派:顺序容器和关联容器。
STL 容器
├── 顺序容器:按插入顺序排列,你放哪它就在哪
│ ├── vector ← 动态数组(最常用!)
│ ├── list ← 双向链表
│ └── deque ← 双端队列
│
└── 关联容器:按"键"自动排序,查找飞快
├── set ← 集合(不重复)
└── map ← 键值对(字典)
2.1 顺序容器——排队的艺术
2.1.1 vector:动态数组(⭐ 最常用!)
vector 就像一个能自动伸缩的数组。用的时候和普通数组一样方便,但它能在运行时自动扩容。
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 创建 vector
vector<int> v; // 空的
vector<int> v2(5, 10); // 5 个元素,全是 10:{10,10,10,10,10}
vector<int> v3 = {1,2,3,4,5}; // 初始化列表
// 尾部添加
v.push_back(10);
v.push_back(20);
v.push_back(30);
// v 现在是 {10, 20, 30}
// 访问元素
cout << v[0] << endl; // 10(下标访问,不检查越界)
cout << v.at(1) << endl; // 20(at 访问,越界会抛异常)
cout << v.front() << endl; // 10(第一个元素)
cout << v.back() << endl; // 30(最后一个元素)
// 大小
cout << "大小: " << v.size() << endl; // 3
// 删除尾部
v.pop_back();
// v 现在是 {10, 20}
// 遍历方式一:下标遍历
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
// 遍历方式二:范围 for(C++11,推荐!)
for (int x : v) {
cout << x << " ";
}
cout << endl;
// 遍历方式三:迭代器
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
💡 vector 的使用场景:需要随机访问、尾部增删频繁、不确定数据量大小——90% 的情况用它就对了。
vector 的内存机制:自动扩容
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
cout << "size=" << v.size()
<< " capacity=" << v.capacity() << endl;
}
// size 是实际元素数量,capacity 是已分配的存储空间
// capacity 会按 1, 2, 4, 8, 16... 的规律翻倍增长
⚠️ 性能提示:如果你提前知道大概需要多少元素,用
v.reserve(n)预分配空间,避免频繁扩容。
2.1.2 list:双向链表
list 就像一条双向通行的链条——在任意位置插入和删除都非常快(O(1)),但不支持随机访问。
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst = {1, 2, 3, 4, 5};
// 头部和尾部操作
lst.push_front(0); // 头部插入:{0,1,2,3,4,5}
lst.push_back(6); // 尾部插入:{0,1,2,3,4,5,6}
// 删除指定值
lst.remove(3); // 删除所有值为 3 的元素:{0,1,2,4,5,6}
// 排序(list 有自己的 sort 成员函数)
lst.push_front(9);
lst.sort(); // {0,1,2,4,5,6,9}
// 反转
lst.reverse(); // {9,6,5,4,2,1,0}
// 遍历
for (int x : lst) {
cout << x << " ";
}
cout << endl;
// ⚠️ list 不支持下标访问!
// lst[0]; // ❌ 编译错误
return 0;
}
💡 list 的使用场景:频繁在中间插入/删除(比如播放列表、任务队列),不需要随机访问。
2.1.3 deque:双端队列
deque(读作"deck")就像一个两头都能进出的队伍——头尾操作都是 O(1),也支持随机访问。
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> dq = {3, 4, 5};
// 两端操作
dq.push_front(2); // 头部插入:{2,3,4,5}
dq.push_front(1); // 头部插入:{1,2,3,4,5}
dq.push_back(6); // 尾部插入:{1,2,3,4,5,6}
// 随机访问
cout << dq[0] << endl; // 1
cout << dq[5] << endl; // 6
// 两端删除
dq.pop_front(); // {2,3,4,5,6}
dq.pop_back(); // {2,3,4,5}
for (int x : dq) cout << x << " "; // 2 3 4 5
cout << endl;
return 0;
}
📊 三种顺序容器对比
| 特性 | vector | list | deque |
|---|---|---|---|
| 底层结构 | 动态数组 | 双向链表 | 分段数组 |
| 随机访问 | ✅ O(1) | ❌ O(n) | ✅ O(1) |
| 尾部增删 | ✅ O(1) | ✅ O(1) | ✅ O(1) |
| 头部增删 | ❌ O(n) | ✅ O(1) | ✅ O(1) |
| 中间增删 | ❌ O(n) | ✅ O(1) | ❌ O(n) |
| 内存效率 | 高(连续) | 低(指针开销) | 中等 |
| 适用场景 | 通用首选 | 频繁中间插入删除 | 两端操作频繁 |
🎯 选择口诀:不确定用啥就选
vector;需要头尾操作选deque;需要中间频繁插删选list。
2.2 关联容器——有"索引"的高效查找
2.2.1 set:集合(不重复的有序集合)
set 就像一个自动去重、自动排序的集合——你扔进去的东西,重复的会被忽略,而且自动按从小到大排好。
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
// 插入
s.insert(5);
s.insert(2);
s.insert(8);
s.insert(2); // 重复!会被忽略
s.insert(1);
// s 自动排序:{1, 2, 5, 8}
// 遍历(自动有序)
for (int x : s) {
cout << x << " ";
}
cout << endl; // 1 2 5 8
// 查找
if (s.find(5) != s.end()) {
cout << "找到了 5!" << endl;
}
// 删除
s.erase(2); // 删除值为 2 的元素
// s: {1, 5, 8}
// 大小
cout << "元素数量: " << s.size() << endl; // 3
// 判断是否存在
cout << "5 存在吗? " << (s.count(5) ? "是" : "否") << endl; // 是
cout << "2 存在吗? " << (s.count(2) ? "是" : "否") << endl; // 否
return 0;
}
💡 set 的使用场景:去重、判断元素是否存在、需要自动排序的集合。
multiset:允许重复的 set
multiset<int> ms = {1, 2, 2, 3, 3, 3};
// ms: {1, 2, 2, 3, 3, 3}
cout << ms.count(3) << endl; // 3(有 3 个 3)
2.2.2 map:键值对(字典)
map 就像一本字典——通过"键"(key)快速查到"值"(value),而且键自动排序。
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> scores;
// 插入(三种方式)
scores["张三"] = 90; // 下标插入
scores.insert({"李四", 85}); // insert 插入
scores.insert(make_pair("王五", 92));
// 访问
cout << "张三的成绩: " << scores["张三"] << endl; // 90
// 遍历(按键自动排序,中文按拼音排序)
for (auto& pair : scores) {
cout << pair.first << ": " << pair.second << endl;
}
// 查找
if (scores.find("李四") != scores.end()) {
cout << "找到了李四!成绩: " << scores["李四"] << endl;
}
// 修改
scores["张三"] = 95; // 张三进步了!
// 删除
scores.erase("王五");
// ⚠️ 注意:用 [] 访问不存在的键会自动插入一个默认值!
cout << scores["赵六"] << endl; // 输出 0,但"赵六"被自动插入了!
// 安全的做法是先用 count 或 find 检查
return 0;
}
💡 map 的使用场景:统计词频、配置管理、任何需要"键→值"映射的场景。
实用案例:统计单词出现次数
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
string words[] = {"apple", "banana", "apple", "cherry", "banana", "apple"};
map<string, int> count;
for (auto& w : words) {
count[w]++; // 如果 w 不存在,会自动创建并初始化为 0,然后 ++
}
cout << "=== 词频统计 ===" << endl;
for (auto& p : count) {
cout << p.first << ": " << p.second << " 次" << endl;
}
// apple: 3 次
// banana: 2 次
// cherry: 1 次
return 0;
}
multimap:允许重复键的 map
multimap<string, int> grades;
grades.insert({"张三", 90});
grades.insert({"张三", 95}); // 张三有两门课的成绩
grades.insert({"李四", 88});
// 查找张三的所有成绩
auto range = grades.equal_range("张三");
for (auto it = range.first; it != range.second; ++it) {
cout << it->first << ": " << it->second << endl;
}
// 张三: 90
// 张三: 95
📊 关联容器对比
| 特性 | set | multiset | map | multimap |
|---|---|---|---|---|
| 存储内容 | 值 | 值 | 键值对 | 键值对 |
| 是否允许重复 | ❌ | ✅ | ❌(键) | ✅(键) |
| 是否自动排序 | ✅ | ✅ | ✅(按键) | ✅(按键) |
| 查找效率 | O(log n) | O(log n) | O(log n) | O(log n) |
| 典型场景 | 去重/存在性判断 | 重复值集合 | 字典/映射 | 一对多映射 |
9.3 STL 算法——几十种现成的"数据处理器"
STL 算法是定义在 <algorithm> 头文件中的函数模板,它们通过迭代器操作容器,不关心容器的具体类型。
3.1 遍历算法:for_each
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(int x) {
cout << x << " ";
}
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// for_each:对每个元素执行一个操作
for_each(v.begin(), v.end(), print); // 1 2 3 4 5
cout << endl;
// 用 Lambda 表达式更简洁(C++11)
for_each(v.begin(), v.end(), [](int x) {
cout << x * x << " ";
});
cout << endl; // 1 4 9 16 25
return 0;
}
3.2 排序算法:sort
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {5, 2, 8, 1, 9, 3};
// 默认升序
sort(v.begin(), v.end());
for (int x : v) cout << x << " "; // 1 2 3 5 8 9
cout << endl;
// 降序排序
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << " "; // 9 8 5 3 2 1
cout << endl;
// 自定义排序:按绝对值排序
vector<int> v2 = {-3, 1, -5, 2, -4};
sort(v2.begin(), v2.end(), [](int a, int b) {
return abs(a) < abs(b); // 按绝对值从小到大
});
for (int x : v2) cout << x << " "; // 1 2 -3 -4 -5
cout << endl;
return 0;
}
3.3 查找算法:find、binary_search
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 3, 5, 7, 9};
// find:线性查找(O(n))
auto it = find(v.begin(), v.end(), 5);
if (it != v.end()) {
cout << "找到了 5,位置: " << (it - v.begin()) << endl; // 位置: 2
}
// binary_search:二分查找(O(log n),但容器必须有序!)
bool found = binary_search(v.begin(), v.end(), 7);
cout << "7 存在吗? " << (found ? "是" : "否") << endl; // 是
// count:统计出现次数
vector<int> v2 = {1, 2, 2, 3, 2, 4};
int cnt = count(v2.begin(), v2.end(), 2);
cout << "2 出现了 " << cnt << " 次" << endl; // 3 次
// find_if:按条件查找
vector<int> v3 = {1, 4, 6, 8, 9};
auto it2 = find_if(v3.begin(), v3.end(), [](int x) {
return x % 2 == 0; // 找第一个偶数
});
if (it2 != v3.end()) {
cout << "第一个偶数: " << *it2 << endl; // 4
}
return 0;
}
3.4 去重算法:unique(搭配 sort 使用)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 3, 2, 3, 1, 2, 4, 3};
// unique 只能去除"相邻"的重复元素,所以要先排序
sort(v.begin(), v.end()); // {1,1,2,2,3,3,3,4}
auto last = unique(v.begin(), v.end()); // 去重,返回新末尾
v.erase(last, v.end()); // 真正删除多余元素
for (int x : v) cout << x << " "; // 1 2 3 4
cout << endl;
return 0;
}
3.5 变换算法:transform
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(5);
// 把 src 的每个元素乘以 2,存到 dst
transform(src.begin(), src.end(), dst.begin(), [](int x) {
return x * 2;
});
for (int x : dst) cout << x << " "; // 2 4 6 8 10
cout << endl;
return 0;
}
3.6 其他常用算法速查
vector<int> v = {5, 2, 8, 1, 9, 3};
// min_element / max_element:最小/最大元素
cout << "最小值: " << *min_element(v.begin(), v.end()) << endl; // 1
cout << "最大值: " << *max_element(v.begin(), v.end()) << endl; // 9
// accumulate:求和(需要 #include <numeric>)
#include <numeric>
int sum = accumulate(v.begin(), v.end(), 0);
cout << "总和: " << sum << endl; // 28
// reverse:反转
reverse(v.begin(), v.end());
for (int x : v) cout << x << " "; // 3 9 1 8 2 5
cout << endl;
// swap:交换两个变量
int a = 1, b = 2;
swap(a, b);
cout << a << " " << b << endl; // 2 1
// next_permutation:下一个排列
vector<int> p = {1, 2, 3};
do {
for (int x : p) cout << x << " ";
cout << endl;
} while (next_permutation(p.begin(), p.end()));
// 输出:123 132 213 231 312 321(全排列!)
9.4 STL 函数对象——让函数也能当"参数"
4.1 什么是函数对象(仿函数)?
函数对象就是重载了 () 运算符的类,它的对象可以像函数一样被调用。
#include <iostream>
using namespace std;
// 定义一个函数对象类
class Multiplier {
int factor;
public:
Multiplier(int f) : factor(f) {}
// 重载 () 运算符
int operator()(int x) const {
return x * factor;
}
};
int main() {
Multiplier times2(2); // 创建一个"乘以2"的函数对象
Multiplier times3(3); // 创建一个"乘以3"的函数对象
cout << times2(5) << endl; // 10(像函数一样调用!)
cout << times3(5) << endl; // 15
// 可以传给 STL 算法
vector<int> v = {1, 2, 3, 4, 5};
transform(v.begin(), v.end(), v.begin(), times2);
for (int x : v) cout << x << " "; // 2 4 6 8 10
cout << endl;
return 0;
}
💡 函数对象比普通函数强大的地方在于:它可以携带状态(成员变量)。
4.2 STL 内置的函数对象
STL 在 <functional> 头文件中提供了一堆现成的函数对象:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 算术类
plus<int> add;
cout << add(3, 4) << endl; // 7
minus<int> sub;
cout << sub(10, 3) << endl; // 7
multiplies<int> mul;
cout << mul(3, 4) << endl; // 12
divides<int> div;
cout << div(10, 3) << endl; // 3
modulus<int> mod;
cout << mod(10, 3) << endl; // 1
negate<int> neg;
cout << neg(5) << endl; // -5
// 关系类
greater<int> gt;
cout << gt(5, 3) << endl; // 1(true)
less<int> lt;
cout << lt(5, 3) << endl; // 0(false)
// 用在排序中
vector<int> v = {5, 2, 8, 1, 9};
sort(v.begin(), v.end(), greater<int>()); // 降序排序
for (int x : v) cout << x << " "; // 9 8 5 2 1
cout << endl;
return 0;
}
4.3 Lambda 表达式:匿名函数对象(C++11)
Lambda 表达式就是一种没有名字的、写在一行里的函数对象。它是函数对象的"语法糖"。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Lambda 表达式语法:
// [捕获列表](参数列表) -> 返回类型 { 函数体 }
// 示例1:打印偶数
for_each(v.begin(), v.end(), [](int x) {
if (x % 2 == 0) cout << x << " ";
});
cout << endl; // 2 4 6 8 10
// 示例2:自定义排序(按个位数排序)
vector<int> v2 = {23, 15, 47, 8, 32};
sort(v2.begin(), v2.end(), [](int a, int b) {
return (a % 10) < (b % 10);
});
for (int x : v2) cout << x << " "; // 32 23 15 47 8
cout << endl;
// 示例3:捕获外部变量
int threshold = 5;
auto count = count_if(v.begin(), v.end(), [threshold](int x) {
return x > threshold; // 捕获了外部的 threshold
});
cout << "大于 " << threshold << " 的有 " << count << " 个" << endl; // 5 个
// 示例4:Lambda 的各种捕获方式
int a = 10, b = 20;
auto f1 = [a]() { cout << a << endl; }; // 值捕获(只读)
auto f2 = [&a]() { a = 100; }; // 引用捕获(可修改)
auto f3 = [=]() { cout << a << b << endl; };// 值捕获所有外部变量
auto f4 = [&]() { a = 1; b = 2; }; // 引用捕获所有外部变量
f2(); // a 被修改为 100
cout << "a = " << a << endl; // 100
return 0;
}
🎯 Lambda 表达式速记:
[]不捕获任何变量[x]值捕获 x(只读)[&x]引用捕获 x(可修改)[=]值捕获所有外部变量[&]引用捕获所有外部变量
4.4 函数对象 vs 普通函数 vs Lambda
// 方式一:普通函数
bool compareDesc(int a, int b) { return a > b; }
// 方式二:函数对象
struct DescCompare {
bool operator()(int a, int b) const { return a > b; }
};
// 方式三:Lambda(最简洁!)
int main() {
vector<int> v = {5, 2, 8, 1, 9};
sort(v.begin(), v.end(), compareDesc); // 普通函数
sort(v.begin(), v.end(), DescCompare()); // 函数对象
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Lambda
// 三种方式效果完全相同!Lambda 最简洁。
}
实战综合案例:学生成绩管理系统
把前面学的所有 STL 知识串起来,写一个完整的例子:
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include <string>
using namespace std;
struct Student {
string name;
int age;
vector<int> scores; // 多门课的成绩
double avgScore() const {
if (scores.empty()) return 0;
return accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
}
};
int main() {
// ===== 1. 用 vector 存储学生信息 =====
vector<Student> students = {
{"张三", 20, {90, 85, 78}},
{"李四", 21, {75, 88, 91}},
{"王五", 19, {95, 92, 88}},
{"赵六", 20, {60, 65, 70}},
{"钱七", 22, {88, 90, 85}}
};
// ===== 2. 用 sort + Lambda 按平均分降序排序 =====
sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.avgScore() > b.avgScore();
});
cout << "=== 成绩排名 ===" << endl;
for (size_t i = 0; i < students.size(); i++) {
cout << "第" << i + 1 << "名: " << students[i].name
<< " 平均分: " << students[i].avgScore() << endl;
}
// ===== 3. 用 map 统计各年龄段人数 =====
map<int, int> ageCount;
for (auto& s : students) {
ageCount[s.age]++;
}
cout << "\n=== 年龄分布 ===" << endl;
for (auto& p : ageCount) {
cout << p.first << " 岁: " << p.second << " 人" << endl;
}
// ===== 4. 用 find_if 查找最高分 =====
auto best = max_element(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.avgScore() < b.avgScore();
});
cout << "\n最高分: " << best->name << " (" << best->avgScore() << ")" << endl;
// ===== 5. 用 count_if 统计及格人数(平均分 >= 60)=====
int passCount = count_if(students.begin(), students.end(),
[](const Student& s) { return s.avgScore() >= 60; });
cout << "及格人数: " << passCount << "/" << students.size() << endl;
// ===== 6. 用 transform 提取所有人的名字 =====
vector<string> names;
transform(students.begin(), students.end(), back_inserter(names),
[](const Student& s) { return s.name; });
cout << "\n=== 所有学生 ===" << endl;
for (auto& name : names) cout << name << " ";
cout << endl;
return 0;
}
输出:
=== 成绩排名 ===
第1名: 王五 平均分: 91.6667
第2名: 钱七 平均分: 87.6667
第3名: 李四 平均分: 84.6667
第4名: 张三 平均分: 84.3333
第5名: 赵六 平均分: 65
=== 年龄分布 ===
19 岁: 1 人
20 岁: 2 人
21 岁: 1 人
22 岁: 1 人
最高分: 王五 (91.6667)
及格人数: 5/5
=== 所有学生 ===
王五 钱七 李四 张三 赵六
总结:STL 知识全景图
STL 编程
│
├── 📦 容器(数据的"房子")
│ ├── 顺序容器:vector / list / deque
│ │ → 按插入顺序存储,选择取决于增删模式
│ └── 关联容器:set / multiset / map / multimap
│ → 按键自动排序,查找效率 O(log n)
│
├── ⚙️ 算法(数据的"处理器")
│ ├── 遍历:for_each
│ ├── 排序:sort
│ ├── 查找:find / binary_search / count / find_if
│ ├── 去重:unique
│ ├── 变换:transform
│ └── 其他:accumulate / reverse / swap / next_permutation
│
├── 🔗 迭代器(容器和算法的"桥梁")
│ → begin() / end() / rbegin() / rend()
│ → 支持 ++ / -- / * / - 等操作
│
└── 🎭 函数对象(算法的"定制化参数")
├── 仿函数:重载 () 的类
├── STL 内置:greater<> / less<> / plus<> 等
└── Lambda:匿名函数对象,最简洁的写法
🎯 一句话总结:STL 的设计哲学是——容器负责存,算法负责处理,迭代器负责连接,函数对象负责定制。四者配合,就能用最少的代码解决最多的问题。
如果觉得有帮助,欢迎点赞收藏 👍 有问题可以在评论区讨论!
更多推荐
所有评论(0)