一、STL 到底是什么?

说实话,我刚学 C++ 那会儿,对 STL 的第一印象就是——这玩意儿也太好用了吧!

STL,全称 Standard Template Library,中文叫标准模板库。它不是 C++ 语言本身的一部分,而是 C++ 标准委员会提供的一套通用库。说白了,就是一群大佬帮你写好了常用的数据结构和算法,你直接拿来用就行。

我在公司做项目时,经常看到新人自己手写链表、自己实现排序。嗯,精神可嘉,但真的没必要。STL 已经帮你搞定了,而且比你写的更稳定、更高效。

STL 的核心思想: 把数据结构和算法分离,通过迭代器作为桥梁,让它们可以灵活组合。

二、STL 六大组件概述

STL 由六大组件组成,我习惯把它们分成三组来理解:

组件 作用 类比
容器(Container) 存储数据 就像各种箱子
迭代器(Iterator) 遍历容器中的元素 就像箱子的把手
算法(Algorithm) 对数据进行操作 就像加工工具
仿函数(Functor) 让对象像函数一样调用 就像可编程的按钮
适配器(Adapter) 对组件进行包装 就像转换插头
分配器(Allocator) 管理内存分配 就像仓库管理员

你可能会问:六个组件都要学吗?我的建议是——先把前四个搞明白。适配器和分配器属于进阶内容,日常开发中用得不多。

三、容器:你的数据该放哪儿?

容器就是用来存放数据的。STL 提供了两大类容器:序列式容器和关联式容器。

3.1 vector —— 动态数组

vector 是我用得最多的容器,没有之一。它就是一个可以自动扩容的数组。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);
    
    // 像数组一样访问
    std::cout << vec[1] << std::endl;  // 输出 20
    
    // 遍历
    for (int i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    return 0;
}

我的经验: 如果你不确定用哪种容器,先用 vector。它性能好,内存连续,缓存友好。我在项目中 80% 的场景都用 vector 搞定了。

3.2 list —— 双向链表

list 是双向链表,适合频繁插入和删除的场景。

#include <list>

std::list<int> myList;
myList.push_back(1);
myList.push_front(0);   // 头部插入
myList.insert(++myList.begin(), 5);  // 在第二个位置插入 5

注意: list 不支持随机访问,你不能写 myList[3]。想访问第 4 个元素?你得从头遍历过去。我曾经在项目里踩过这个坑,当时还纳闷为什么编译报错……

3.3 map —— 键值对字典

map 存储的是键值对,键是唯一的。底层用红黑树实现,查找很快。

#include <map>
#include <string>

std::map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
scores["Charlie"] = 92;

// 查找
if (scores.find("Bob") != scores.end()) {
    std::cout << "Bob's score: " << scores["Bob"];
}

3.4 set —— 集合

set 就是数学里的集合,元素唯一且自动排序。

#include <set>

std::set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(3);  // 重复插入无效

// 输出:1 2 3(自动排序)
for (int x : s) {
    std::cout << x << " ";
}

四、迭代器:容器的通用访问方式

迭代器是什么?你可以把它想象成一个指针,但它比指针更聪明。

不同的容器有不同的迭代器类型:

迭代器类型 支持的操作 典型容器
输入迭代器 只读,单向移动 istream_iterator
输出迭代器 只写,单向移动 ostream_iterator
前向迭代器 读写,单向移动 forward_list
双向迭代器 读写,双向移动 list, set, map
随机访问迭代器 读写,任意跳转 vector, deque
#include <vector>
#include <iostream>

std::vector<int> vec = {10, 20, 30, 40, 50};

// 使用迭代器遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";  // *it 获取元素
}

// C++11 范围 for 循环(底层也是迭代器)
for (int x : vec) {
    std::cout << x << " ";
}

小技巧: 我习惯用 auto 声明迭代器,省得写长长的类型名。比如 auto it = vec.begin(),编译器会自动推导类型。

五、算法:STL 的灵魂

算法是 STL 最强大的部分。你不需要自己写排序、查找,直接调用就行。

5.1 sort —— 排序

#include <algorithm>
#include <vector>

std::vector<int> v = {5, 2, 8, 1, 9};
std::sort(v.begin(), v.end());  // 升序排序

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

5.2 find —— 查找

#include <algorithm>
#include <vector>

std::vector<int> v = {10, 20, 30, 40, 50};
auto it = std::find(v.begin(), v.end(), 30);

if (it != v.end()) {
    std::cout << "找到了: " << *it;
} else {
    std::cout << "没找到";
}

5.3 for_each —— 遍历处理

#include <algorithm>
#include <vector>
#include <iostream>

std::vector<int> v = {1, 2, 3, 4, 5};

// 打印每个元素
std::for_each(v.begin(), v.end(), [](int x) {
    std::cout << x * 2 << " ";  // 输出:2 4 6 8 10
});

六、仿函数:让对象像函数一样调用

仿函数,说白了就是重载了 operator() 的类。它为什么有用?因为它可以保存状态。

#include <iostream>

class Adder {
public:
    Adder(int n) : num(n) {}
    
    int operator()(int x) {
        return x + num;
    }

private:
    int num;
};

int main() {
    Adder add5(5);
    std::cout << add5(10);  // 输出 15
    std::cout << add5(20);  // 输出 25
    return 0;
}

你看,add5 是一个对象,但它用起来就像函数。而且它内部保存了 num=5,每次调用都基于这个状态。

实际应用: 仿函数经常和算法搭配使用。比如 sort 的第三个参数,你可以传一个仿函数来自定义排序规则。

七、知识体系总览

下面这张图是我自己整理的 STL 核心知识结构,你看一眼就能明白各个组件之间的关系:

STL 六大组件关系图 容器 Container 迭代器 Iterator 算法 Algorithm 提供 连接 操作 仿函数 Functor 适配器 Adapter 分配器 Allocator 自定义操作 包装 内存管理 核心思想:数据(容器)与操作(算法)分离 迭代器作为桥梁,仿函数提供灵活的操作策略

八、避坑指南

最后,分享几个我踩过的坑:

  • 迭代器失效: 对 vector 进行插入或删除操作后,之前的迭代器可能就失效了。我曾经在循环里删除元素,结果程序直接崩溃。解决办法是用 erase 返回的新迭代器。
  • map 的 [] 操作: 用 map[key] 访问不存在的键时,会自动插入一个默认值。如果你只是想查找,用 find 更安全。
  • sort 要求随机访问迭代器: list 不能用 sort,得用 list 自己的 sort 成员函数。

我的建议: 刚开始学 STL,先把 vector、map、sort、find 这四个用熟。它们能解决你 90% 的日常需求。其他的,遇到再学也不迟。

好了,STL 入门就讲到这里。记住一句话:STL 不是让你重新发明轮子,而是让你站在巨人的肩膀上写代码。

公众号:蓝海资料掘金营,微信 deep3321