C++ STL标准模板库详解

C++ STL标准模板库详解

简介

STL(Standard Template Library,标准模板库)是C++标准库的重要组成部分,它提供了一套通用的、可复用的组件,包括容器、迭代器、算法、函数对象、适配器和空间配置器。本文重点讲解最常用的三大组件------容器、迭代器和算法,并通过丰富的代码示例帮助开发者掌握STL的核心用法。

一、STL 总览

1.1 六大组件

组件 说明 头文件示例
容器(Container) 存储和组织数据的数据结构 <vector>, <list>, <map>
迭代器(Iterator) 连接容器和算法的桥梁 各容器头文件
算法(Algorithm) 操作容器内容的模板函数 <algorithm>
仿函数(Functor) 重载了 operator() 的类 <functional>
适配器(Adapter) 修改容器或函数接口的包装器 <stack>, <queue>
空间配置器(Allocator) 负责内存分配与释放 <memory>

1.2 容器分类

复制代码
STL 容器
├── 序列容器(Sequential)
│   ├── vector    向量(动态数组)
│   ├── deque     双端队列
│   └── list      双向链表
├── 关联容器(Associative)
│   ├── set       集合(唯一键)
│   ├── multiset  多重集合
│   ├── map       映射(键值对)
│   └── multimap  多重映射
└── 容器适配器(Adapter)
    ├── stack       栈(LIFO)
    ├── queue       队列(FIFO)
    └── priority_queue  优先队列

图片占位符:STL容器分类结构图

二、vector(向量)

2.1 简介

vector 是一种动态数组,能自动管理内存,支持随机访问。在尾部插入和删除元素的时间复杂度为 O(1),在中间或头部操作为 O(n)。

cpp 复制代码
#include <vector>
using namespace std;

2.2 构造方式

cpp 复制代码
vector<int> vec1;                              // 默认构造,空向量
vector<int> vec2(10);                          // 10 个元素,值为 0
vector<int> vec3(10, 5);                       // 10 个元素,值为 5
vector<int> vec4(vec3);                        // 拷贝构造
vector<int> vec5(vec3.begin(), vec3.end());    // 区间构造

int arr[] = {1, 2, 3, 4, 5};
vector<int> vec6(arr, arr + 5);               // 从数组构造

2.3 元素访问

cpp 复制代码
vector<int> vec = {10, 20, 30, 40, 50};

vec[0];          // 下标访问(不检查越界)
vec.at(2);       // at 访问(越界抛出异常)
vec.front();     // 第一个元素
vec.back();      // 最后一个元素
vec.data();      // 返回底层数组指针

2.4 添加与删除

cpp 复制代码
vector<int> vec;

// 添加元素
vec.push_back(10);                    // 尾部添加
vec.emplace_back(20);                 // 尾部原地构造(C++11,更高效)
vec.insert(vec.begin(), 5);           // 在指定位置插入
vec.insert(vec.begin() + 2, 3, 100);  // 插入 3 个 100

// 删除元素
vec.pop_back();                       // 删除末尾元素
vec.erase(vec.begin());               // 删除指定位置
vec.erase(vec.begin(), vec.begin()+2);// 删除区间
vec.clear();                          // 清空

// 重新赋值
vec.assign(7, 100);                   // 7 个 100
vec.assign(vec2.begin(), vec2.end()); // 用另一个容器赋值

2.5 容量与大小

cpp 复制代码
vector<int> vec = {1, 2, 3};

vec.size();       // 元素个数:3
vec.empty();      // 是否为空:false
vec.capacity();   // 当前容量(>= size)
vec.reserve(100); // 预分配容量(避免多次重新分配)
vec.resize(10);   // 改变大小(多出的元素默认初始化)
vec.shrink_to_fit(); // 释放多余容量(C++11)

2.6 完整使用示例

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    const char *str = "Hello STL";

    vector<int> vec_i(arr, arr + 5);
    vector<char> vec_c(str, str + strlen(str));

    // 使用迭代器遍历
    for (vector<int>::iterator it = vec_i.begin(); it != vec_i.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    // 输出:1 2 3 4 5

    // 使用下标遍历
    for (size_t i = 0; i < vec_c.size(); i++) {
        cout << vec_c[i];
    }
    cout << endl;
    // 输出:Hello STL

    return 0;
}

2.7 vector 的注意事项

  • 不支持前插vector 没有提供 push_front,因为在头部插入需要移动所有元素
  • 迭代器失效:插入或删除元素可能导致迭代器失效,需要重新获取
  • reserve 优化 :如果知道大致元素数量,提前 reserve 可以避免多次内存重分配

三、deque(双端队列)

3.1 简介

deque(Double-Ended Queue)与 vector 类似,支持随机访问,但额外支持在头部高效插入和删除。

cpp 复制代码
#include <deque>
using namespace std;

3.2 与 vector 的区别

特性 vector deque
头部插入 不支持 支持 push_front
内存分布 连续内存 分段连续
capacity/reserve 支持 不支持
随机访问 O(1) O(1)

3.3 使用示例

cpp 复制代码
#include <iostream>
#include <deque>
using namespace std;

void print_deque(deque<int> que, const char *name) {
    cout << "========= " << name << endl;
    for (auto it = que.begin(); it != que.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
}

int main() {
    deque<int> que(5, 0);       // 5 个 0
    print_deque(que, "initQue");
    // 输出:0 0 0 0 0

    que.push_back(12);
    que.push_back(13);
    print_deque(que, "push_back");
    // 输出:0 0 0 0 0 12 13

    que.push_front(1);
    que.push_front(2);
    print_deque(que, "push_front");
    // 输出:2 1 0 0 0 0 0 12 13

    que.pop_back();
    que.pop_back();
    print_deque(que, "pop_back");
    // 输出:2 1 0 0 0 0 0

    que.pop_front();
    que.pop_front();
    print_deque(que, "pop_front");
    // 输出:0 0 0 0 0

    return 0;
}

四、list(双向链表)

4.1 简介

list 是由节点组成的双向链表,不支持随机访问(没有 operator[]),但在任意位置插入和删除元素的时间复杂度都是 O(1)。

cpp 复制代码
#include <list>
using namespace std;

4.2 与 vector/deque 的区别

特性 vector deque list
随机访问 O(1) O(1) 不支持
头部插入 不支持 O(1) O(1)
中间插入/删除 O(n) O(n) O(1)
内存开销 高(每个节点额外指针)

4.3 list 特有操作

cpp 复制代码
list<int> lst1 = {123, 0, 34, 1123};
list<int> lst2 = {12, 100};

lst1.sort();         // 排序
lst2.sort();         // 排序
// lst1: 0, 34, 123, 1123
// lst2: 12, 100

lst1.merge(lst2);    // 合并两个有序链表
// lst1: 0, 12, 34, 100, 123, 1123
// lst2: 空(元素被转移到 lst1)

lst1.reverse();      // 反转
lst1.unique();       // 去除连续重复元素
lst1.splice(it, lst2); // 将 lst2 的元素拼接到 lst1 的指定位置

4.4 使用示例

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

void PrintIt(list<int> n) {
    for (list<int>::iterator iter = n.begin(); iter != n.end(); ++iter)
        cout << *iter << " ";
}

int main() {
    list<int> listn1, listn2;
    listn1.push_back(123);
    listn1.push_back(0);
    listn1.push_back(34);
    listn1.push_back(1123);

    listn2.push_back(100);
    listn2.push_back(12);

    listn1.sort();
    listn2.sort();

    PrintIt(listn1);  // 0 34 123 1123
    cout << endl;
    PrintIt(listn2);  // 12 100

    listn1.merge(listn2);
    cout << endl;
    PrintIt(listn1);  // 0 12 34 100 123 1123

    return 0;
}

五、set / multiset(集合)

5.1 set 简介

set 是有序的唯一元素集合,底层实现为红黑树。元素既充当数据又充当关键字,最大的特点是元素唯一自动排序

cpp 复制代码
#include <set>
using namespace std;

5.2 基本用法

cpp 复制代码
set<int, less<int>> myset;

myset.insert(30);
myset.insert(10);
myset.insert(20);
myset.insert(10);  // 插入失败,元素已存在

// 遍历(自动排序)
for (auto it = myset.begin(); it != myset.end(); ++it) {
    cout << *it << " ";  // 输出:10 20 30
}

// 查找
auto it = myset.find(20);
if (it != myset.end()) {
    cout << "找到:" << *it << endl;
}

// 删除
myset.erase(10);
myset.erase(myset.begin());

// 大小
myset.size();
myset.empty();

// count:存在返回 1,不存在返回 0
myset.count(20);

5.3 multiset

multisetset 的区别在于允许重复元素:

cpp 复制代码
multiset<int, less<int>> myset;
myset.insert(10);
myset.insert(10);  // 成功,允许重复
myset.count(10);   // 返回 2

六、map / multimap(映射)

6.1 map 简介

map 存储键值对(key-value),通过唯一的 key 快速查找 value。底层实现为红黑树,key 自动排序。与 set 不同,map 的 key 和 value 是分开的。

cpp 复制代码
#include <map>
using namespace std;

6.2 基本用法

cpp 复制代码
map<char, int, less<char>> mymap;

// 插入
mymap.insert(pair<char, int>('a', 100));
mymap.insert(map<char, int>::value_type('b', 200));
mymap['c'] = 300;  // 使用下标操作符

// 遍历
for (auto it = mymap.begin(); it != mymap.end(); ++it) {
    cout << it->first << " => " << it->second << endl;
}
// a => 100
// b => 200
// c => 300

// 查找
auto it = mymap.find('b');
if (it != mymap.end()) {
    cout << "找到:" << it->second << endl;
}

// 下标访问
mymap['a'];   // 返回 100
mymap['d'];   // 如果不存在,创建并初始化为 0

6.3 map 与 set 的关键区别

特性 set map
元素 数据即关键字 key-value 分离
下标运算 不支持 支持 []
访问方式 *it it->first, it->second

6.4 multimap

multimap 允许 key 重复,不支持 [] 运算符

cpp 复制代码
multimap<int, string> mmap;
mmap.insert(pair<int, string>(1, "hello"));
mmap.insert(pair<int, string>(1, "world"));  // 允许重复 key
mmap.insert(pair<int, string>(2, "foo"));

// 查找某个 key 的所有值
auto range = mmap.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
    cout << it->second << endl;
}

6.5 注意事项

  • setmap 的 key 只能存在一个,insert 重复值会失败
  • multisetmultimap 允许重复 key
  • 关联容器的 find() 成员函数比泛型 find() 算法效率更高(O(log n) vs O(n))

七、容器适配器

7.1 stack(栈)

后进先出(LIFO):

cpp 复制代码
#include <stack>

stack<int> stk;
stk.push(10);
stk.push(20);
stk.push(30);

stk.top();    // 30
stk.pop();    // 移除 30
stk.size();   // 2
stk.empty();  // false

7.2 queue(队列)

先进先出(FIFO):

cpp 复制代码
#include <queue>

queue<int> q;
q.push(10);
q.push(20);
q.push(30);

q.front();   // 10
q.back();    // 30
q.pop();     // 移除 10
q.size();    // 2

7.3 priority_queue(优先队列)

元素按优先级自动排序(默认大顶堆):

cpp 复制代码
#include <queue>

priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(20);

pq.top();    // 30(最大值)
pq.pop();    // 移除 30

// 自定义小顶堆
priority_queue<int, vector<int>, greater<int>> min_pq;

八、迭代器

8.1 迭代器的作用

迭代器是连接容器和算法的桥梁,类似于指向容器元素的指针。可以递增迭代器使其依次指向容器中的每个元素。

8.2 迭代器类型

类型 能力 支持的容器
输入迭代器 只读,单向 istream
输出迭代器 只写,单向 ostream
前向迭代器 读写,单向 forward_list
双向迭代器 读写,双向 list, set, map
随机访问迭代器 读写,随机 vector, deque

8.3 迭代器基本操作

cpp 复制代码
vector<int> vec = {1, 2, 3, 4, 5};

// 获取迭代器
vector<int>::iterator it = vec.begin();     // 首元素
vector<int>::iterator end = vec.end();      // 尾后元素(不包含)

// C++11 自动类型推导
auto it2 = vec.begin();

// const 迭代器(只读)
vector<int>::const_iterator cit = vec.cbegin();

// 遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
    cout << *it << " ";
}

// 范围 for 循环(C++11)
for (auto& elem : vec) {
    cout << elem << " ";
}

8.4 begin/end 与 cbegin/cend

cpp 复制代码
// begin() 和 end() 返回的类型根据容器是否为 const 而定
// cbegin() 和 cend()(C++11)始终返回 const_iterator
auto it = vec.cbegin();  // 不论 vec 是否为 const,返回只读迭代器

8.5 迭代器失效问题

以下操作可能导致迭代器失效:

cpp 复制代码
vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();

vec.push_back(6);  // 可能导致 it 失效(重新分配内存)
vec.insert(vec.begin() + 1, 10);  // it 之后的位置可能失效
vec.erase(vec.begin());  // it 及之后的迭代器失效

// 安全做法:操作后重新获取迭代器
it = vec.begin();

九、常用算法

9.1 非修改序列算法

cpp 复制代码
#include <algorithm>

vector<int> vec = {1, 2, 3, 4, 5};

// 查找
auto it = find(vec.begin(), vec.end(), 3);

// 计数
int n = count(vec.begin(), vec.end(), 3);

// 遍历
for_each(vec.begin(), vec.end(), [](int x) {
    cout << x << " ";
});

9.2 修改序列算法

cpp 复制代码
vector<int> vec1 = {1, 2, 3};
vector<int> vec2(3);

// 复制
copy(vec1.begin(), vec1.end(), vec2.begin());

// 填充
fill(vec2.begin(), vec2.end(), 0);

// 变换
transform(vec1.begin(), vec1.end(), vec2.begin(),
    [](int x) { return x * 2; });

// 替换
replace(vec1.begin(), vec1.end(), 2, 20);

// 移除(逻辑删除,不改变大小)
auto new_end = remove(vec1.begin(), vec1.end(), 20);
vec1.erase(new_end, vec1.end());  // 物理删除

// 反转
reverse(vec1.begin(), vec1.end());

9.3 排序算法

cpp 复制代码
vector<int> vec = {5, 2, 8, 1, 9};

// 排序(默认升序)
sort(vec.begin(), vec.end());

// 自定义排序
sort(vec.begin(), vec.end(), greater<int>());  // 降序
sort(vec.begin(), vec.end(), [](int a, int b) {
    return a > b;
});

// 稳定排序
stable_sort(vec.begin(), vec.end());

// 部分排序
partial_sort(vec.begin(), vec.begin() + 3, vec.end());

// 检查是否有序
is_sorted(vec.begin(), vec.end());

9.4 查找算法(有序区间)

cpp 复制代码
vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 二分查找
bool found = binary_search(vec.begin(), vec.end(), 5);

// 下界/上界
auto lower = lower_bound(vec.begin(), vec.end(), 5);  // 第一个 >= 5
auto upper = upper_bound(vec.begin(), vec.end(), 5);  // 第一个 > 5

9.5 数值算法

cpp 复制代码
#include <numeric>

vector<int> vec = {1, 2, 3, 4, 5};

// 累加
int sum = accumulate(vec.begin(), vec.end(), 0);

// 内积
int product = inner_product(vec.begin(), vec.end(), vec.begin(), 0);

// 部分和
vector<int> result(5);
partial_sum(vec.begin(), vec.end(), result.begin());
// result: 1, 3, 6, 10, 15

十、forward_list(前向链表,C++11)

cpp 复制代码
#include <forward_list>

forward_list<int> fl = {1, 2, 3, 4};

// 单向链表,迭代器只能 ++
// 不支持 size() 操作
// 提供 before_begin() 获取头节点前位置

fl.push_front(0);
fl.insert_after(fl.before_begin(), -1);

for (auto it = fl.begin(); it != fl.end(); ++it) {
    cout << *it << " ";
}

十一、容器选择指南

需求场景 推荐容器 原因
随机访问,尾部增删 vector 连续内存,缓存友好
头尾增删 deque 支持两端高效操作
频繁中间插入/删除 list 链表结构,O(1) 操作
查找唯一元素 set / unordered_set 红黑树/哈希表
键值对查找 map / unordered_map 红黑树/哈希表
LIFO 操作 stack 适配器封装
FIFO 操作 queue 适配器封装
带优先级的队列 priority_queue 堆结构

十二、string 容器

严格来说,string 不属于STL容器,但它支持与容器相似的操作:

cpp 复制代码
#include <string>
using namespace std;

string s1 = "Hello";
string s2 = "World";

s1 + s2;           // 拼接
s1.size();         // 长度
s1.substr(0, 3);   // 子串
s1.find("ell");    // 查找
s1[0];             // 下标访问
s1.empty();        // 是否为空

// C++11 数字与字符串转换
int n = stoi("123");
string str = to_string(456);

十三、C++/C 混合调用注意事项

在 C++ 中调用 C 编写的库时,必须使用 extern "C" 包裹头文件,否则由于 C++ 的名称修饰机制,链接器无法找到正确的符号。

cpp 复制代码
extern "C" {
    #include "c_library.h"
}

这个规则适用于所有 C 编写的静态库和动态库。

总结

STL 是 C++ 开发者必须掌握的核心工具库。合理使用 STL 容器和算法,不仅能提高开发效率,还能保证代码的质量和性能。

关键原则:

  • 根据使用场景选择合适的容器(随机访问选 vector,频繁插入选 list,查找选 map/set)
  • 理解迭代器的种类和失效机制,避免使用失效的迭代器
  • 善用泛型算法,减少手写循环
  • 优先使用 emplace 系列函数减少不必要的对象拷贝
  • 注意 STL 的线程安全性:读操作是线程安全的,写操作需要加锁

原始笔记来源: frasight/C++笔记.cpp(STL 部分)

相关推荐
li1670902701 小时前
第二十五章:C++11(下)
c语言·开发语言·数据结构·c++
承渊政道1 小时前
【动态规划算法】(回文串问题解题框架与经典案例)
数据结构·c++·学习·算法·leetcode·动态规划·哈希算法
AI进化营-智能译站1 小时前
ROS2 C++开发系列11-VS Code一键生成Doxygen注释|让ROS2节点文档自动跟上代码迭代
java·数据库·c++·ai
zhouwy1133 小时前
Linux文件系统与IO编程
linux·c++
王老师青少年编程10 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
叼烟扛炮11 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
样例过了就是过了12 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
谭欣辰13 小时前
C++ 排列组合完整指南
开发语言·c++·算法
橙子也要努力变强13 小时前
信号捕捉底层机制-机理篇2
linux·服务器·c++