STL 算法与迭代器终极指南:从基础到高级应用
STL(Standard Template Library,标准模板库)是 C++ 中最强大的工具之一,而算法与迭代器作为 STL 的核心组件,为开发者提供了高效、灵活的数据处理能力。本文将全面解析 STL 算法与迭代器的工作原理、常用操作及高级技术,帮助你充分利用 STL 提升代码质量与开发效率。
一、迭代器:STL 的 "胶水"
迭代器(Iterator)是连接容器与算法的桥梁,它提供了一种统一致一的方式来访问容器中的元素,而无需暴露容器的内部实现细节。
1.1 迭代器的本质与分类
迭代器本质上是一种行为类似指针的对象,它重载了*
、->
、++
、--
等运算符,使开发者可以像操作指针一样操作容器元素。
STL 将迭代器分为五大类别,按功能从弱到强排列:
- 输入迭代器(Input Iterator):只能读取元素,且只能单向移动(++),如
istream_iterator
- 输出迭代器(Output Iterator):只能写入元素,且只能单向单向单向移动(++),如
ostream_iterator
- 前向迭代器(Forward Iterator):可读写元素,单向移动,支持多次赋值,如
forward_list
的迭代器 - 双向迭代器(Bidirectional Iterator):可读写元素,双向移动(++、--),如
list
、set
、map
的迭代器 - 随机访问迭代器(Random Access Iterator):支持所有迭代器操作,还支持随机访问(
+=
、-=
、[]
等),如vector
、deque
、array
的迭代器
1.2 迭代器的基本操作
所有迭代器都支持的基本操作:
// 假设it是一个迭代器
*it; // 解引用,获取当前元素
++it; // 移动到下一个元素
it++;
it1 == it2; // 判断两个两个迭代器是否指向同一位置
it1 != it2;
随机访问迭代器额外支持:
it + n; // 移动n个位置
it - n;
it += n;
it -= n;
it1 - it2; // 计算两个迭代器之间的距离
it[n]; // 访问第n个元素
it1 < it2; // 比较位置
it1 > it2;
it1 <= it2;
it1 >= it2;
1.3 容器与迭代器的对应关系
不同容器提供的迭代器类型不同:
容器 | 迭代器类型 |
---|---|
vector | 随机访问 |
deque | 随机访问 |
list | 双向 |
set/multiset | 双向 |
map/multimap | 双向 |
unordered_set/unordered_map | 前向 |
forward_list | 前向 |
array | 随机访问 |
1.4 迭代器适配器
STL 提供了几种特殊的迭代器适配器:
反向迭代器(reverse_iterator):
vector<int> v = {1, 2, 3, 4}; for (auto it = v.rbegin(); it != v.rend(); ++it) {cout << *it << " "; // 输出:4 3 2 1 }
插入迭代器(insert iterator):
back_inserter
:在容器末尾插入元素front_inserter
:在容器开头插入元素inserter
:在指定位置插入元素
vector<int> v1 = {1, 2, 3}; vector<int> v2;// 将v1的元素复制到v2 copy(v1.begin(), v1.end(), back_inserter(v2));
流迭代器(stream iterator):
// 从标准输入读取整数 istream_iterator<int> in(cin), eof; vector<int> v(in, eof);// 输出到标准输出 ostream_iterator<int> out(cout, " "); copy(v.begin(), v.end(), out);
移动迭代器(move_iterator):C++11 引入,用于实现元素的移动而非复制
二、STL 算法概览
STL 算法是一系列模板函数,用于处理容器中的元素,实现了各种常用操作。这些算法定义在<algorithm>
头文件中,部分数值算法在<numeric>
中。
2.1 算法的分类
STL 算法可分为以下几大类:
- 非修改性序列操作:不改变容器中的元素,如
for_each
、find
、count
- 修改性序列操作:改变容器中的元素,如
copy
、transform
、replace
- 排序与相关操作:如
sort
、merge
、binary_search
- 数值算法:如
accumulate
、inner_product
- 集合算法:如
set_union
、set_intersection
- 关系算法:如
equal
、lexicographical_compare
2.2 算法的参数模式
大多数 STL 算法遵循一致的参数模式:
- 前两个参数是迭代器,指定操作的范围
[first, last)
- 后续参数根据算法功能而定
例如:
// 查找范围内等于value的元素
find(first, last, value);// 对范围内的每个元素应用func
for_each(first, last, func);
三、常用 STL 算法详解
3.1 非修改性算法
3.1.1 遍历元素:for_each
for_each
对范围内的每个元素应用函数:
vector<int> v = {1, 2, 3, 4, 5};// 使用函数
void print(int x) {cout << x << " ";
}
for_each(v.begin(), v.end(), print);// 使用lambda表达式(C++11)
for_each(v.begin(), v.end(), [](int x) {cout << x << " ";
});
3.1.2 查找元素:find, find_if, find_if_not
vector<int> v = {1, 2, 3, 4, 5};// 查找值为3的元素
auto it = find(v.begin(), v.end(), 3);// 查找第一个偶数
it = find_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// 查找第一个非偶数(C++11)
it = find_if_not(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
3.1.3 计数元素:count, count_if
vector<int> v = {1, 2, 3, 4, 5, 3};// 计数等于3的元素
int cnt = count(v.begin(), v.end(), 3);// 计数偶数的个数
cnt = count_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
3.1.4 检查条件:all_of, any_of, none_of (C++11)
vector<int> v = {1, 3, 5, 7, 9};// 所有元素都是奇数?
bool all_odd = all_of(v.begin(), v.end(), [](int x) {return x % 2 != 0;
});// 有偶数吗?
bool has_even = any_of(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// 没有负数?
bool no_negative = none_of(v.begin(), v.end(), [](int x) {return x < 0;
});
3.2 修改性算法
3.2.1 复制元素:copy, copy_if, copy_n
vector<int> v = {1, 2, 3, 4, 5};
vector<int> dest(5);// 复制整个范围
copy(v.begin(), v.end(), dest.begin());// 复制偶数(C++11)
vector<int> evens;
copy_if(v.begin(), v.end(), back_inserter(evens), [](int x) {return x % 2 == 0;
});// 复制前3个元素(C++11)
vector<int> first_three;
copy_n(v.begin(), 3, back_inserter(first_three));
3.2.2 转换元素:transform
transform
可以将一个或两个范围的元素经过函数转换后写入目标范围:
vector<int> v = {1, 2, 3, 4, 5};
vector<int> res;// 单参数版本:将每个元素乘以2
transform(v.begin(), v.end(), back_inserter(res), [](int x) {return x * 2;
});// 双参数版本:两个范围元素相加
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> sum(3);
transform(a.begin(), a.end(), b.begin(), sum.begin(), [](int x, int y) {return x + y;
});
3.2.3 替换元素:replace, replace_if, replace_copy
vector<int> v = {1, 2, 3, 4, 5};// 将3替换为30
replace(v.begin(), v.end(), 3, 30);// 将偶数替换为0
replace_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
}, 0);// 复制的同时替换,不改变原容器
vector<int> replaced;
replace_copy(v.begin(), v.end(), back_inserter(replaced), 0, 100);
3.2.4 移除元素:remove, remove_if, remove_copy
vector<int> v = {1, 2, 3, 4, 5, 3};// 移除所有3(注意:实际是将元素前移,返回新的结束迭代器)
auto new_end = remove(v.begin(), v.end(), 3);
v.erase(new_end, v.end()); // 真正删除元素// 移除所有偶数
new_end = remove_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
v.erase(new_end, v.end());// 复制的同时移除
vector<int> odds;
remove_copy_if(v.begin(), v.end(), back_inserter(odds), [](int x) {return x % 2 == 0;
});
3.3 排序与相关算法
3.3.1 排序:sort, stable_sort
vector<int> v = {3, 1, 4, 1, 5, 9};// 默认升序排序
sort(v.begin(), v.end());// 降序排序
sort(v.begin(), v.end(), greater<int>());// 自定义排序:按绝对值
sort(v.begin(), v.end(), [](int a, int b) {return abs(a) < abs(b);
});// stable_sort保持相等元素的相对顺序
vector<pair<int, int>> vp = {{2, 1}, {1, 2}, {2, 3}};
stable_sort(vp.begin(), vp.end(), [](const auto& a, const auto& b) {return a.first < b.first;
});
// 结果: {{1,2}, {2,1}, {2,3}} 保持了first=2的元素的相对顺序
3.3.2 二分查找:binary_search, lower_bound, upper_bound
二分查找要求容器已排序:
vector<int> v = {1, 3, 5, 7, 9};// 检查元素是否存在
bool has_5 = binary_search(v.begin(), v.end(), 5);// 查找第一个 >= 5的元素
auto it_low = lower_bound(v.begin(), v.end(), 5);// 查找第一个 > 5的元素
auto it_high = upper_bound(v.begin(), v.end(), 5);// 在自定义排序的容器中使用
vector<int> v_abs = {1, -2, 3, -4};
sort(v_abs.begin(), v_abs.end(), [](int a, int b) {return abs(a) < abs(b);
});
// 查找时必须使用相同的比较函数
auto it = lower_bound(v_abs.begin(), v_abs.end(), 3, [](int a, int b) {return abs(a) < abs(b);
});
3.3.3 打乱与分区:shuffle, partition
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9};// 随机打乱元素(C++11)
random_device rd;
mt19937 g(rd());
shuffle(v.begin(), v.end(), g);// 分区:将偶数放前面,奇数放后面
auto partition_it = partition(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// stable_partition保持同组元素的相对顺序
auto stable_partition_it = stable_partition(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
3.4 数值算法
数值算法定义在<numeric>
头文件中:
#include <numeric>
3.4.1 累加:accumulate
vector<int> v = {1, 2, 3, 4, 5};// 求和,初始值为0
int sum = accumulate(v.begin(), v.end(), 0);// 求乘积,初始值为1
int product = accumulate(v.begin(), v.end(), 1, [](int a, int b) {return a * b;
});// 拼接字符串
vector<string> strs = {"Hello", " ", "World", "!"};
string result = accumulate(strs.begin(), strs.end(), string(""));
3.4.2 内积:inner_product
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};// 计算 1*4 + 2*5 + 3*6 = 32
int ip = inner_product(a.begin(), a.end(), b.begin(), 0);// 自定义运算:(1+4) * (2+5) * (3+6) = 5*7*9=315
int custom_ip = inner_product(a.begin(), a.end(), b.begin(), 1,[](int a, int b) { return a * b; }, // 外运算[](int a, int b) { return a + b; }); // 内运算
3.4.3 前缀和:partial_sum
vector<int> v = {1, 2, 3, 4, 5};
vector<int> prefix_sums(v.size());// 计算前缀和:1, 1+2, 1+2+3, ...
partial_sum(v.begin(), v.end(), prefix_sums.begin());// 自定义前缀运算:计算前缀乘积
partial_sum(v.begin(), v.end(), prefix_sums.begin(), [](int a, int b) { return a * b; });
3.5 集合算法
集合算法用于处理有序范围:
3.5.1 集合交、并、差:set_intersection, set_union, set_difference
vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = {3, 4, 5, 6, 7};
vector<int> result;// 交集:3,4,5
result.resize(min(a.size(), b.size()));
auto it = set_intersection(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());// 并集:1,2,3,4,5,6,7
result.resize(a.size() + b.size());
it = set_union(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());// 差集:a - b = 1,2
result.resize(a.size());
it = set_difference(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());
四、迭代器与算法的高级技术
4.1 迭代器的算术运算与距离
对于随机访问迭代器,可以进行算术运算:
vector<int> v = {1, 2, 3, 4, 5};// 计算迭代器之间的距离
ptrdiff_t n = v.end() - v.begin(); // 结果为5// 移动迭代器
auto it = v.begin() + 2; // 指向3// 使用distance函数(适用于所有迭代器)
n = distance(v.begin(), v.end()); // 结果为5
4.2 算法的复杂度考量
STL 算法都有明确定义的时间复杂度,了解这些有助于选择合适的算法:
- O (1):如
advance
(随机访问迭代器) - O (n):如
for_each
、find
、count
- O (n log n):如
sort
、stable_sort
- O (n + m):如
set_union
(n 和 m 是两个范围的大小) - O (log n):如
binary_search
4.3 自定义迭代器
虽然 STL 提供了丰富的迭代器,但有时我们需要创建自定义迭代器。实现自定义迭代器需要定义以下类型和运算符:
template <typename T>
class MyIterator {
public:// 必须定义的五种迭代器关联类型using value_type = T;using reference = T&;using pointer = T*;using difference_type = ptrdiff_t;using iterator_category = std::random_access_iterator_tag;// 构造函数MyIterator(pointer ptr) : m_ptr(ptr) {}// 解引用运算符reference operator*() const { return *m_ptr; }pointer operator->() const { return m_ptr; }// 递增运算符MyIterator& operator++() { // 前置++m_ptr++;return *this;}MyIterator operator++(int) { // 后置++MyIterator temp = *this;m_ptr++;return temp;}// 递减运算符(双向迭代器及以上需要)MyIterator& operator--() { // 前置--m_ptr--;return *this;}MyIterator operator--(int) { // 后置--MyIterator temp = *this;m_ptr--;return temp;}// 算术运算符(随机访问迭代器需要)MyIterator operator+(difference_type n) const {return MyIterator(m_ptr + n);}MyIterator operator-(difference_type n) const {return MyIterator(m_ptr - n);}// 复合赋值运算符(随机访问迭代器需要)MyIterator& operator+=(difference_type n) {m_ptr += n;return *this;}MyIterator& operator-=(difference_type n) {m_ptr -= n;return *this;}// 下标运算符(随机访问迭代器需要)reference operator[](difference_type n) const {return m_ptr[n];}// 比较运算符bool operator==(const MyIterator& other) const {return m_ptr == other.m_ptr;}bool operator!=(const MyIterator& other) const {return !(*this == other);}// 关系运算符(随机访问迭代器需要)bool operator<(const MyIterator& other) const {return m_ptr < other.m_ptr;}bool operator>(const MyIterator& other) const {return other < *this;}bool operator<=(const MyIterator& other) const {return !(*this > other);}bool operator>=(const MyIterator& other) const {return !(*this < other);}private:pointer m_ptr;
};
4.4 算法的并行化(C++17)
C++17 引入了算法的并行版本,通过执行策略参数来指定并行方式:
#include <execution> // 包含并行执行策略vector<int> v(1000000);// 串行执行(默认)
sort(v.begin(), v.end());// 并行执行(C++17)
sort(execution::par, v.begin(), v.end());// 并行+向量执行(C++17)
sort(execution::par_unseq, v.begin(), v.end());
并行算法可以显著提高大数据量处理的效率,但需要注意:
- 并行算法可能会有额外的开销,对于小数据量可能不如串行算法
- 传递给并行算法的函数必须是线程安全的
- 并非所有算法都有并行版本
五、实战经验与最佳实践
5.1 选择合适的迭代器类型
- 当需要高效随机访问时,优先选择
vector
或deque
- 当需要频繁插入 / 删除元素时,优先选择
list
并使用双向迭代器 - 对于只读操作,使用
const_iterator
可以提供更好的安全性和潜在的优化
5.2 避免常见陷阱
迭代器失效:当容器发生修改(插入 / 删除元素)时,迭代器可能失效
vector<int> v = {1, 2, 3, 4}; auto it = v.begin() + 2; // 指向3 v.erase(it); // 此时it失效,不能再使用
算法与容器的匹配:确保算法要求的迭代器类型与容器提供的一致
list<int> l = {3, 1, 4, 1, 5}; // 错误:sort要求随机访问迭代器,而list提供双向迭代器 // sort(l.begin(), l.end()); // 正确:使用list自带的sort方法 l.sort();
正确处理
remove
的结果:remove
并不真正删除元素,只是将它们移到容器末尾vector<int> v = {1, 2, 3, 2, 4}; // 错误:v的大小仍然是5 // remove(v.begin(), v.end(), 2);// 正确:使用erase删除尾部的"垃圾"元素 auto new_end = remove(v.begin(), v.end(), 2); v.erase(new_end, v.end()); // 现在v的大小是3
5.3 提高性能的技巧
预留空间:使用
reserve
避免多次内存分配vector<int> v; v.reserve(1000); // 预留空间 for (int i = 0; i < 1000; ++i) {v.push_back(i); }
使用
emplace
系列函数:直接在容器中构造元素,避免复制vector<pair<int, string>> v; // 避免:创建临时对象再复制 // v.push_back({1, "one"});// 更好:直接在容器中构造 v.emplace_back(1, "one");
选择合适的算法:例如,
find
对于无序容器是 O (n),而unordered_set::find
是 O (1) 平均复杂度
六、总结
STL 的算法与迭代器是 C++ 中强大的工具,它们提供了一致的接口来处理各种数据结构,大大提高了代码的复用性和效率。掌握迭代器的分类和特性,熟悉常用算法的功能和复杂度,能够帮助你写出更简洁、高效、可维护的 C++ 代码。
通过本文的学习,你应该已经对 STL 算法与迭代器有了全面的了解。但 STL 的学习是一个持续的过程,建议在实际开发中多使用、多实践,同时查阅官方文档和源码,深入理解其内部实现原理,这样才能真正发挥 STL 的强大威力。
记住,最好的学习方式是实践。尝试用 STL 算法重写你现有代码中的循环和处理逻辑,你会惊讶于 STL 带来的便捷和高效!