C++ STL 标准库详解

什么是 STL?

STL(Standard Template Library,标准模板库)是 C++ 标准库的重要组成部分,它提供了一组通用的模板类和函数,用于实现常见的数据结构和算法。STL 的设计理念是泛型编程,通过模板技术实现了数据结构和算法的高度复用。

STL 的组成部分

STL 主要由以下六个部分组成:

  1. 容器(Containers):存储数据的对象
  2. 迭代器(Iterators):用于遍历容器中的元素
  3. 算法(Algorithms):操作容器中元素的函数
  4. 函数对象(Functors):具有函数行为的对象
  5. 适配器(Adapters):修改其他组件接口的组件
  6. 分配器(Allocators):负责内存分配和释放

常用容器

1. 序列容器

序列容器按照线性顺序存储元素,支持随机访问或顺序访问。

1.1 vector

vector 是最常用的序列容器,它是一个动态数组,支持随机访问。

常用函数

函数 功能 时间复杂度
push_back() 在末尾添加元素 均摊 O(1)
pop_back() 删除末尾元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)
clear() 清空容器 O(n)
resize() 调整容器大小 O(n)
reserve() 预留空间 O(n)
operator[] 随机访问元素 O(1)
at() 带边界检查的随机访问 O(1)
begin() 返回起始迭代器 O(1)
end() 返回结束迭代器 O(1)

使用示例

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <vector>

int main() {
// 创建 vector
std::vector<int> v;

// 添加元素
v.push_back(10);
v.push_back(20);
v.push_back(30);

// 遍历元素
for (int i = 0; i < v.size(); ++i) {
std::cout << v[i] << " "; // 10 20 30
}
std::cout << std::endl;

// 使用迭代器遍历
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " "; // 10 20 30
}
std::cout << std::endl;

// 使用范围 for 循环(C++11+)
for (int x : v) {
std::cout << x << " "; // 10 20 30
}
std::cout << std::endl;

// 访问元素
std::cout << "第一个元素: " << v.front() << std::endl; // 10
std::cout << "最后一个元素: " << v.back() << std::endl; // 30

// 删除元素
v.pop_back(); // 删除最后一个元素

// 检查大小
std::cout << "大小: " << v.size() << std::endl; // 2

// 清空容器
v.clear();
std::cout << "是否为空: " << (v.empty() ? "是" : "否") << std::endl; // 是

return 0;
}

1.2 list

list 是双向链表,支持快速的插入和删除操作,但不支持随机访问。

常用函数

函数 功能 时间复杂度
push_back() 在末尾添加元素 O(1)
push_front() 在开头添加元素 O(1)
pop_back() 删除末尾元素 O(1)
pop_front() 删除开头元素 O(1)
insert() 在指定位置插入元素 O(1)
erase() 删除指定位置的元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)
clear() 清空容器 O(n)
begin() 返回起始迭代器 O(1)
end() 返回结束迭代器 O(1)

使用示例

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <list>

int main() {
// 创建 list
std::list<int> lst;

// 添加元素
lst.push_back(10);
lst.push_back(20);
lst.push_front(5);

// 遍历元素
for (int x : lst) {
std::cout << x << " "; // 5 10 20
}
std::cout << std::endl;

// 插入元素
auto it = lst.begin();
++it; // 指向 10
lst.insert(it, 7); // 在 5 和 10 之间插入 7

for (int x : lst) {
std::cout << x << " "; // 5 7 10 20
}
std::cout << std::endl;

// 删除元素
it = lst.begin();
++it; // 指向 7
lst.erase(it); // 删除 7

for (int x : lst) {
std::cout << x << " "; // 5 10 20
}
std::cout << std::endl;

// 其他操作
std::cout << "大小: " << lst.size() << std::endl; // 3
lst.clear();
std::cout << "是否为空: " << (lst.empty() ? "是" : "否") << std::endl; // 是

return 0;
}

1.3 deque

deque 是双端队列,支持在两端快速插入和删除,也支持随机访问。

常用函数

函数 功能 时间复杂度
push_back() 在末尾添加元素 O(1)
push_front() 在开头添加元素 O(1)
pop_back() 删除末尾元素 O(1)
pop_front() 删除开头元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)
clear() 清空容器 O(n)
operator[] 随机访问元素 O(1)
at() 带边界检查的随机访问 O(1)
begin() 返回起始迭代器 O(1)
end() 返回结束迭代器 O(1)

使用示例

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
30
31
32
33
#include <iostream>
#include <deque>

int main() {
// 创建 deque
std::deque<int> dq;

// 添加元素
dq.push_back(10);
dq.push_back(20);
dq.push_front(5);

// 遍历元素
for (int x : dq) {
std::cout << x << " "; // 5 10 20
}
std::cout << std::endl;

// 随机访问
std::cout << "第一个元素: " << dq[0] << std::endl; // 5
std::cout << "第二个元素: " << dq[1] << std::endl; // 10

// 删除元素
dq.pop_front();
dq.pop_back();

for (int x : dq) {
std::cout << x << " "; // 10
}
std::cout << std::endl;

return 0;
}

2. 关联容器

关联容器按照键值对存储元素,支持快速查找。

2.1 map

map 是有序关联容器,存储键值对,按键自动排序,键唯一。

常用函数

函数 功能 时间复杂度
insert() 插入键值对 O(log n)
erase() 删除元素 O(log n)
find() 查找键 O(log n)
count() 统计键的个数 O(log n)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)
clear() 清空容器 O(n)
begin() 返回起始迭代器 O(1)
end() 返回结束迭代器 O(1)
operator[] 访问或插入元素 O(log n)
at() 访问元素(带检查) O(log n)

使用示例

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
30
31
32
33
34
35
#include <iostream>
#include <map>
#include <string>

int main() {
// 创建 map
std::map<std::string, int> scores;

// 插入元素
scores["Alice"] = 95;
scores["Bob"] = 88;
scores.insert({"Charlie", 92});

// 遍历元素
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
// Alice: 95
// Bob: 88
// Charlie: 92
}

// 查找元素
auto it = scores.find("Bob");
if (it != scores.end()) {
std::cout << "Bob's score: " << it->second << std::endl; // 88
}

// 删除元素
scores.erase("Charlie");

// 检查大小
std::cout << "Size: " << scores.size() << std::endl; // 2

return 0;
}

2.2 set

set 是有序关联容器,存储唯一元素,自动排序。

常用函数

函数 功能 时间复杂度
insert() 插入元素 O(log n)
erase() 删除元素 O(log n)
find() 查找元素 O(log n)
count() 统计元素个数 O(log n)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)
clear() 清空容器 O(n)
begin() 返回起始迭代器 O(1)
end() 返回结束迭代器 O(1)

使用示例

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
30
31
32
33
34
#include <iostream>
#include <set>

int main() {
// 创建 set
std::set<int> s;

// 插入元素
s.insert(10);
s.insert(20);
s.insert(10); // 重复元素,不会插入

// 遍历元素
for (int x : s) {
std::cout << x << " "; // 10 20
}
std::cout << std::endl;

// 查找元素
auto it = s.find(15);
if (it == s.end()) {
std::cout << "15 not found" << std::endl;
}

// 删除元素
s.erase(10);

for (int x : s) {
std::cout << x << " "; // 20
}
std::cout << std::endl;

return 0;
}

2.3 unordered_map

unordered_map 是无序关联容器,使用哈希表实现,查找速度更快。

常用函数:与 map 类似,但时间复杂度为平均 O(1)。

使用示例

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
#include <iostream>
#include <unordered_map>
#include <string>

int main() {
// 创建 unordered_map
std::unordered_map<std::string, int> scores;

// 插入元素
scores["Alice"] = 95;
scores["Bob"] = 88;
scores["Charlie"] = 92;

// 遍历元素(顺序不确定)
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

// 查找元素
auto it = scores.find("Bob");
if (it != scores.end()) {
std::cout << "Bob's score: " << it->second << std::endl;
}

return 0;
}

2.4 unordered_set

unordered_set 是无序关联容器,使用哈希表实现,存储唯一元素。

常用函数:与 set 类似,但时间复杂度为平均 O(1)。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <unordered_set>

int main() {
// 创建 unordered_set
std::unordered_set<int> s;

// 插入元素
s.insert(10);
s.insert(20);
s.insert(10); // 重复元素,不会插入

// 遍历元素(顺序不确定)
for (int x : s) {
std::cout << x << " ";
}
std::cout << std::endl;

return 0;
}

3. 容器适配器

容器适配器是对现有容器的封装,提供特定的接口。

3.1 stack

stack 是栈适配器,提供后进先出(LIFO)的操作。

常用函数

函数 功能 时间复杂度
push() 入栈 O(1)
pop() 出栈 O(1)
top() 获取栈顶元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)

使用示例

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 <stack>

int main() {
// 创建 stack
std::stack<int> st;

// 入栈
st.push(10);
st.push(20);
st.push(30);

// 获取栈顶元素
std::cout << "Top: " << st.top() << std::endl; // 30

// 出栈
st.pop();
std::cout << "Top after pop: " << st.top() << std::endl; // 20

// 检查大小
std::cout << "Size: " << st.size() << std::endl; // 2

return 0;
}

3.2 queue

queue 是队列适配器,提供先进先出(FIFO)的操作。

常用函数

函数 功能 时间复杂度
push() 入队 O(1)
pop() 出队 O(1)
front() 获取队首元素 O(1)
back() 获取队尾元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <queue>

int main() {
// 创建 queue
std::queue<int> q;

// 入队
q.push(10);
q.push(20);
q.push(30);

// 获取队首和队尾元素
std::cout << "Front: " << q.front() << std::endl; // 10
std::cout << "Back: " << q.back() << std::endl; // 30

// 出队
q.pop();
std::cout << "Front after pop: " << q.front() << std::endl; // 20

return 0;
}

3.3 priority_queue

priority_queue 是优先队列适配器,自动保持最大元素在队首。

常用函数

函数 功能 时间复杂度
push() 入队 O(log n)
pop() 出队 O(log n)
top() 获取队首元素 O(1)
size() 返回元素个数 O(1)
empty() 检查是否为空 O(1)

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <queue>

int main() {
// 创建 priority_queue
std::priority_queue<int> pq;

// 入队
pq.push(30);
pq.push(10);
pq.push(20);

// 获取队首元素(最大值)
std::cout << "Top: " << pq.top() << std::endl; // 30

// 出队
pq.pop();
std::cout << "Top after pop: " << pq.top() << std::endl; // 20

return 0;
}

迭代器

迭代器是连接容器和算法的桥梁,提供了遍历容器元素的统一接口。

迭代器类型

迭代器类型 功能 支持的操作
输入迭代器 只读,单向移动 ++, *
输出迭代器 只写,单向移动 ++, *
前向迭代器 读写,单向移动 ++, *, ==, !=
双向迭代器 读写,双向移动 ++, --, *, ==, !=
随机访问迭代器 读写,随机访问 ++, --, +, -, [], *, ==, !=, <, >, etc.

常用迭代器操作

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 <vector>

int main() {
std::vector<int> v = {10, 20, 30, 40, 50};

// 正向迭代器
std::cout << "Forward: ";
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " "; // 10 20 30 40 50
}
std::cout << std::endl;

// 反向迭代器
std::cout << "Reverse: ";
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " "; // 50 40 30 20 10
}
std::cout << std::endl;

// 随机访问
auto it = v.begin();
std::cout << "First element: " << *it << std::endl; // 10
std::cout << "Third element: " << *(it + 2) << std::endl; // 30
std::cout << "Last element: " << *(v.end() - 1) << std::endl; // 50

return 0;
}

常用算法

STL 提供了大量的算法,定义在 <algorithm> 头文件中。

1. 遍历算法

for_each

对容器中的每个元素执行操作。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <vector>
#include <algorithm>

void print(int x) {
std::cout << x << " ";
}

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

// 使用函数
std::for_each(v.begin(), v.end(), print); // 1 2 3 4 5
std::cout << std::endl;

// 使用 lambda
std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x * 2 << " "; // 2 4 6 8 10
});
std::cout << std::endl;

return 0;
}

2. 查找算法

find

查找元素,返回第一个匹配的迭代器。

使用示例

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

int main() {
std::vector<int> v = {10, 20, 30, 40, 50};

// 查找元素
auto it = std::find(v.begin(), v.end(), 30);
if (it != v.end()) {
std::cout << "Found: " << *it << " at position: " << it - v.begin() << std::endl;
} else {
std::cout << "Not found" << std::endl;
}

return 0;
}

find_if

根据条件查找元素。

使用示例

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

int main() {
std::vector<int> v = {10, 25, 30, 35, 40};

// 查找第一个大于 30 的元素
auto it = std::find_if(v.begin(), v.end(), [](int x) {
return x > 30;
});

if (it != v.end()) {
std::cout << "Found: " << *it << std::endl; // 35
}

return 0;
}

3. 排序算法

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
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};

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

std::cout << "Ascending: ";
for (int x : v) {
std::cout << x << " "; // 1 1 2 3 4 5 6 9
}
std::cout << std::endl;

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

std::cout << "Descending: ";
for (int x : v) {
std::cout << x << " "; // 9 6 5 4 3 2 1 1
}
std::cout << std::endl;

// 自定义排序
std::sort(v.begin(), v.end(), [](int a, int b) {
return a % 2 < b % 2; // 偶数在前,奇数在后
});

std::cout << "Custom: ";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;

return 0;
}

4. 修改算法

transform

对元素进行转换。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> result(v.size());

// 转换元素(乘以 2)
std::transform(v.begin(), v.end(), result.begin(), [](int x) {
return x * 2;
});

std::cout << "Original: ";
for (int x : v) std::cout << x << " "; // 1 2 3 4 5
std::cout << std::endl;

std::cout << "Transformed: ";
for (int x : result) std::cout << x << " "; // 2 4 6 8 10
std::cout << std::endl;

return 0;
}

replace

替换元素。

使用示例

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

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

// 替换所有 2 为 99
std::replace(v.begin(), v.end(), 2, 99);

std::cout << "After replace: ";
for (int x : v) {
std::cout << x << " "; // 1 99 3 99 5
}
std::cout << std::endl;

return 0;
}

5. 数值算法

需要包含 <numeric> 头文件。

accumulate

计算元素的累加和。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <numeric>

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

// 计算总和
int sum = std::accumulate(v.begin(), v.end(), 0);
std::cout << "Sum: " << sum << std::endl; // 15

// 计算乘积
int product = std::accumulate(v.begin(), v.end(), 1, [](int a, int b) {
return a * b;
});
std::cout << "Product: " << product << std::endl; // 120

return 0;
}

函数对象

函数对象(Functor)是重载了 operator() 的类,具有函数行为。

使用示例

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 <iostream>
#include <vector>
#include <algorithm>

// 函数对象
class GreaterThan {
private:
int value;
public:
GreaterThan(int v) : value(v) {}

bool operator()(int x) const {
return x > value;
}
};

int main() {
std::vector<int> v = {10, 20, 30, 40, 50};

// 使用函数对象
GreaterThan gt(25);
auto it = std::find_if(v.begin(), v.end(), gt);

if (it != v.end()) {
std::cout << "First element > 25: " << *it << std::endl; // 30
}

return 0;
}

适配器

适配器用于修改其他组件的接口。

1. 容器适配器

如前所述的 stackqueuepriority_queue

2. 迭代器适配器

reverse_iterator

反向迭代器,用于反向遍历容器。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>

int main() {
std::vector<int> v = {10, 20, 30, 40, 50};

// 使用反向迭代器
std::cout << "Reverse: ";
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " "; // 50 40 30 20 10
}
std::cout << std::endl;

return 0;
}

3. 函数适配器

C++11 后推荐使用 lambda 表达式,函数适配器使用较少。

分配器

分配器负责容器的内存管理,通常使用默认分配器即可。

自定义分配器示例

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <vector>

// 简单的自定义分配器(仅作示例)
template <typename T>
class MyAllocator {
public:
typedef T value_type;

MyAllocator() noexcept {}

template <typename U>
MyAllocator(const MyAllocator<U>&) noexcept {}

T* allocate(std::size_t n) {
std::cout << "Allocating " << n << " elements" << std::endl;
return static_cast<T*>(std::malloc(n * sizeof(T)));
}

void deallocate(T* p, std::size_t n) {
std::cout << "Deallocating " << n << " elements" << std::endl;
std::free(p);
}
};

template <typename T, typename U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) noexcept {
return true;
}

template <typename T, typename U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) noexcept {
return false;
}

int main() {
// 使用自定义分配器
std::vector<int, MyAllocator<int>> v;
v.push_back(10);
v.push_back(20);

std::cout << "Size: " << v.size() << std::endl;

return 0;
}

STL 的最佳实践

1. 容器选择

场景 推荐容器 理由
随机访问,频繁在末尾添加/删除 vector 随机访问 O(1),末尾操作高效
频繁在中间插入/删除 list 插入/删除 O(1)
频繁在两端插入/删除 deque 两端操作 O(1)
键值对,需要排序 map 自动排序,查找 O(log n)
键值对,不需要排序 unordered_map 查找平均 O(1)
唯一元素,需要排序 set 自动排序,查找 O(log n)
唯一元素,不需要排序 unordered_set 查找平均 O(1)
后进先出操作 stack 符合栈的特性
先进先出操作 queue 符合队列的特性
优先级操作 priority_queue 自动保持优先级

2. 性能优化

  • 使用 reserve():对于 vector,预先分配足够的空间
  • 避免不必要的复制:使用移动语义和 emplace_back()
  • 选择合适的容器:根据操作类型选择最优容器
  • 使用 unordered_* 容器:对于频繁查找的场景
  • 避免在循环中修改容器:可能导致迭代器失效

3. 常见陷阱

  • 迭代器失效:修改容器后,迭代器可能失效
  • 内存泄漏:容器中的指针需要手动管理
  • 性能退化:不正确的容器选择会导致性能问题
  • 异常安全:需要考虑异常情况下的资源管理

总结

C++ STL 是一个功能强大、设计优雅的标准库,它提供了丰富的容器、算法和工具,极大地提高了开发效率和代码质量。

STL 的核心优势在于:

  1. 代码复用:通过模板技术实现泛型编程
  2. 性能优化:底层实现经过精心优化
  3. 接口统一:提供了一致的编程接口
  4. 可扩展性:支持自定义类型和算法

掌握 STL 的使用,对于编写高效、简洁、可维护的 C++ 代码至关重要。通过合理选择容器、灵活使用算法,可以大大简化开发工作,提高代码质量。

参考资料

  • C++ 标准库文档
  • 《C++ Primer》
  • 《Effective STL》
  • 《STL 源码剖析》